Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
<apache.httpcomponents.httpclient.version>4.5.13</apache.httpcomponents.httpclient.version>
<apache.httpcomponents.httpcore.version>4.4.16</apache.httpcomponents.httpcore.version>
<auto-value.version>1.11.0</auto-value.version>
<jackson.version>2.17.2</jackson.version>
<gson.version>2.12.1</gson.version>
<auto-value-gson.version>1.3.1</auto-value-gson.version>
<junit.version>5.11.4</junit.version>
<mockito.version>3.12.4</mockito.version>
</properties>
Expand Down Expand Up @@ -103,14 +104,14 @@
<artifactId>api-common</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>${jackson.version}</version>
<groupId>com.ryanharter.auto.value</groupId>
<artifactId>auto-value-gson</artifactId>
<version>${auto-value-gson.version}</version>
</dependency>
<!-- JUnit 5 -->
<dependency>
Expand Down Expand Up @@ -170,6 +171,11 @@
<version>3.13.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.ryanharter.auto.value</groupId>
<artifactId>auto-value-gson</artifactId>
<version>${auto-value-gson.version}</version>
</path>
<path>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
Expand Down
79 changes: 51 additions & 28 deletions src/main/java/com/google/genai/Common.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,59 @@

package com.google.genai;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.Iterator;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.util.Map;
import java.util.Set;
import org.jspecify.annotations.Nullable;

/** Common utility methods for the GenAI SDK. */
public final class Common {

private Common() {}

static void setValueByPath(ObjectNode jsonObject, String[] path, Object value) {
static void setValueByPath(JsonObject jsonObject, String[] path, Object value) {
if (path == null || path.length == 0) {
throw new IllegalArgumentException("Path cannot be empty.");
}
if (jsonObject == null) {
throw new IllegalArgumentException("JsonObject cannot be null.");
}

ObjectNode currentObject = jsonObject;
JsonObject currentObject = jsonObject;
for (int i = 0; i < path.length - 1; i++) {
String key = path[i];
if (key.endsWith("[0]")) {
String keyName = key.substring(0, key.length() - 3);
ArrayNode arrayNode;
JsonArray arrayNode;
if (!currentObject.has(keyName)) {
currentObject.putArray(keyName);
arrayNode = (ArrayNode) currentObject.get(keyName);
arrayNode.add(JsonSerializable.objectMapper.createObjectNode());
arrayNode = new JsonArray();
arrayNode.add(new JsonObject());
currentObject.add(keyName, arrayNode);
}
arrayNode = (ArrayNode) currentObject.get(keyName);
currentObject = (ObjectNode) arrayNode.get(0);
arrayNode = (JsonArray) currentObject.get(keyName);
currentObject = arrayNode.get(0).getAsJsonObject();
} else {
if (!currentObject.has(key)) {
currentObject.putObject(key);
currentObject.add(key, new JsonObject());
}
currentObject = (ObjectNode) currentObject.get(key);
currentObject = currentObject.getAsJsonObject(key);
}
}

currentObject.put(path[path.length - 1], JsonSerializable.toJsonNode(value));
currentObject.add(path[path.length - 1], toJsonElement(value));
}

static String formatMap(String template, JsonNode data) {
Iterator<Map.Entry<String, JsonNode>> fields = data.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> field = fields.next();
String key = field.getKey();
static String formatMap(String template, JsonObject data) {
Set<Map.Entry<String, JsonElement>> entries = data.entrySet();
for (Map.Entry<String, JsonElement> entry : entries) {
String key = entry.getKey();
String placeholder = "{" + key + "}";
if (template.contains(placeholder)) {
template = template.replace(placeholder, data.get(key).asText());
template = template.replace(placeholder, entry.getValue().getAsString());
}
}
return template;
Expand All @@ -91,23 +93,23 @@ static boolean isZero(Object obj) {
return false;
}

static Object getValueByPath(JsonNode object, String[] keys) {
static JsonElement getValueByPath(JsonElement object, String[] keys) {
if (object == null || keys == null) {
return null;
}
if (keys.length == 1 && keys[0].equals("_self")) {
return object;
}

JsonNode currentObject = object;
JsonElement currentElement = object;

for (String key : keys) {
if (currentObject instanceof ObjectNode) {
currentObject = currentObject.get(key);
} else if (currentObject instanceof ArrayNode) {
if (currentElement.isJsonObject()) {
currentElement = currentElement.getAsJsonObject().get(key);
} else if (currentElement.isJsonArray()) {
try {
int index = Integer.parseInt(key);
currentObject = currentObject.get(index);
currentElement = currentElement.getAsJsonArray().get(index);
} catch (NumberFormatException | IndexOutOfBoundsException e) {
return null;
}
Expand All @@ -116,6 +118,27 @@ static Object getValueByPath(JsonNode object, String[] keys) {
}
}

return currentObject;
return currentElement;
}

private static @Nullable JsonElement toJsonElement(Object value) {
if (value == null) {
return null;
} else if (value instanceof Number) {
return new JsonPrimitive((Number) value);
} else if (value instanceof String) {
return new JsonPrimitive((String) value);
} else if (value instanceof Boolean) {
return new JsonPrimitive((Boolean) value);
} else if (value instanceof Character) {
return new JsonPrimitive(String.valueOf(value));
} else if (value instanceof JsonObject) {
return (JsonObject) value;
} else if (value instanceof JsonArray) {
return (JsonArray) value;
} else {
Gson gson = new Gson();
return gson.toJsonTree(value);
}
}
}
16 changes: 10 additions & 6 deletions src/main/java/com/google/genai/HttpApiResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package com.google.genai;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonObject;
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
Expand Down Expand Up @@ -47,11 +47,13 @@ private String getErrorMessage() {
return "";
}

JsonNode errorNode = JsonSerializable.objectMapper.readTree(responseBody).get("error");
if (errorNode != null && errorNode.isObject()) {
JsonNode messageNode = errorNode.get("message");
if (messageNode != null && messageNode.isTextual()) {
return messageNode.asText();
JsonObject jsonObject = JsonSerializable.gson.fromJson(responseBody, JsonObject.class);
if (jsonObject != null && jsonObject.has("error") && jsonObject.get("error").isJsonObject()) {
JsonObject errorObject = jsonObject.getAsJsonObject("error");
if (errorObject.has("message")
&& errorObject.get("message").isJsonPrimitive()
&& errorObject.get("message").getAsJsonPrimitive().isString()) {
return errorObject.get("message").getAsString();
}
}
return "";
Expand All @@ -65,6 +67,7 @@ private String getErrorMessage() {
*
* @throws HttpException if the HTTP status code is not 200 OK.
*/
@Override
public HttpEntity getEntity() throws HttpException {
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
Expand All @@ -78,6 +81,7 @@ public HttpEntity getEntity() throws HttpException {
}

/** Closes the Http response. */
@Override
public void close() throws IOException {
this.response.close();
}
Expand Down
122 changes: 91 additions & 31 deletions src/main/java/com/google/genai/JsonSerializable.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,58 +16,118 @@

package com.google.genai;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.ryanharter.auto.value.gson.GenerateTypeAdapter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Optional;
import org.jspecify.annotations.Nullable;

/** A class that can be serialized to JSON and deserialized from JSON. */
public abstract class JsonSerializable {

protected static final ObjectMapper objectMapper = new ObjectMapper();
protected static final Gson gson;

static {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_ABSENT);
objectMapper.registerModule(new Jdk8Module());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
gson =
new GsonBuilder()
.registerTypeAdapterFactory(GenerateTypeAdapter.FACTORY)
.registerTypeAdapter(
Optional.class,
new JsonSerializer<Optional<?>>() {
@Override
public @Nullable JsonElement serialize(
Optional<?> src, Type typeOfSrc, JsonSerializationContext context) {
return src.isPresent() ? context.serialize(src.get()) : null;
}
})
.registerTypeAdapter(
Optional.class,
new JsonDeserializer<Optional<?>>() {
@Override
public Optional<?> deserialize(
JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
if (json.isJsonNull()) {
return Optional.empty();
} else {
Type actualType = null;
if (typeOfT instanceof ParameterizedType) {
actualType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
}
return Optional.of(context.deserialize(json, actualType));
}
}
})
.setExclusionStrategies(
new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
// Skip fields with null values
try {
Object value = f.getDeclaringClass().getDeclaredField(f.getName()).get(f);
return value == null; // If field is null, exclude it
} catch (IllegalAccessException | NoSuchFieldException e) {
return false;
}
}

@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false; // Don't skip classes by default
}
})
.create();
}

/** Serializes an object to a Json string. */
protected static String toJsonString(Object object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
return gson.toJson(object);
}

/** Serializes the instance to a Json string. */
public String toJson() {
return toJsonString(this);
}

/** Serializes an object to a JsonNode. */
protected static JsonNode toJsonNode(Object object) {
return objectMapper.valueToTree(object);
/** Deserializes a Json string to an object of the given type. */
public static <T extends JsonSerializable> T fromJsonString(String jsonString, Class<T> clazz) {
return gson.fromJson(jsonString, clazz);
}

/** Deserializes a Json string to an object of the given type. */
public static <T extends JsonSerializable> T fromJsonString(String jsonString, Class<T> clazz) {
try {
return objectMapper.readValue(jsonString, clazz);
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
public static Object fromJsonString(String jsonString, Type typeOfT) {
return gson.fromJson(jsonString, typeOfT);
}

/** Deserializes a Json string to an object of the given type. */
public static JsonElement fromJsonString(String jsonString) {
return JsonParser.parseString(jsonString);
}

/** Deserializes a JsonElement to an object of the given type. */
public static <T extends JsonSerializable> T fromJsonElement(
JsonElement jsonElement, Class<T> clazz) {
return gson.fromJson(jsonElement, clazz);
}

/** Serializes the instance to a Json string. */
public JsonElement toJsonElement() {
return gson.toJsonTree(this);
}

/** Deserializes a JsonNode to an object of the given type. */
public static <T extends JsonSerializable> T fromJsonNode(JsonNode jsonNode, Class<T> clazz) {
try {
return objectMapper.treeToValue(jsonNode, clazz);
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
/** Serializes an object to a JsonElement. */
protected static JsonElement toJsonElement(Object object) {
return gson.toJsonTree(object);
}
}
Loading