diff --git a/components/serialization/json/spotBugsExcludeFilter.xml b/components/serialization/json/spotBugsExcludeFilter.xml
index d0f06c8a..4a40a57f 100644
--- a/components/serialization/json/spotBugsExcludeFilter.xml
+++ b/components/serialization/json/spotBugsExcludeFilter.xml
@@ -18,6 +18,7 @@ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubu
+
@@ -31,4 +32,4 @@ xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubu
-
\ No newline at end of file
+
diff --git a/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/DefaultGsonBuilder.java b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/DefaultGsonBuilder.java
new file mode 100644
index 00000000..f19a651f
--- /dev/null
+++ b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/DefaultGsonBuilder.java
@@ -0,0 +1,163 @@
+package com.microsoft.kiota.serialization;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import com.microsoft.kiota.PeriodAndDuration;
+
+import jakarta.annotation.Nonnull;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Base64;
+
+public class DefaultGsonBuilder {
+
+ private static final TypeAdapter OFFSET_DATE_TIME =
+ new TypeAdapter() {
+ @Override
+ public OffsetDateTime read(JsonReader in) throws IOException {
+ String stringValue = in.nextString();
+ try {
+ return OffsetDateTime.parse(stringValue);
+ } catch (DateTimeParseException ex) {
+ // Append UTC offset if it's missing
+ try {
+ LocalDateTime localDateTime = LocalDateTime.parse(stringValue);
+ return localDateTime.atOffset(ZoneOffset.UTC);
+ } catch (DateTimeParseException ex2) {
+ throw new JsonSyntaxException(
+ "Failed parsing '"
+ + stringValue
+ + "' as OffsetDateTime; at path "
+ + in.getPreviousPath(),
+ ex2);
+ }
+ }
+ }
+
+ @Override
+ public void write(JsonWriter out, OffsetDateTime value) throws IOException {
+ out.value(value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+ }
+ };
+
+ private static final TypeAdapter LOCAL_DATE =
+ new TypeAdapter() {
+ @Override
+ public LocalDate read(JsonReader in) throws IOException {
+ String stringValue = in.nextString();
+ try {
+ return LocalDate.parse(stringValue);
+ } catch (DateTimeParseException ex) {
+ throw new JsonSyntaxException(
+ "Failed parsing '"
+ + stringValue
+ + "' as LocalDate; at path "
+ + in.getPreviousPath(),
+ ex);
+ }
+ }
+
+ @Override
+ public void write(JsonWriter out, LocalDate value) throws IOException {
+ out.value(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
+ }
+ };
+
+ private static final TypeAdapter LOCAL_TIME =
+ new TypeAdapter() {
+ @Override
+ public LocalTime read(JsonReader in) throws IOException {
+ String stringValue = in.nextString();
+ try {
+ return LocalTime.parse(stringValue);
+ } catch (DateTimeParseException ex) {
+ throw new JsonSyntaxException(
+ "Failed parsing '"
+ + stringValue
+ + "' as LocalTime; at path "
+ + in.getPreviousPath(),
+ ex);
+ }
+ }
+
+ @Override
+ public void write(JsonWriter out, LocalTime value) throws IOException {
+ out.value(value.format(DateTimeFormatter.ISO_LOCAL_TIME));
+ }
+ };
+
+ private static final TypeAdapter PERIOD_AND_DURATION =
+ new TypeAdapter() {
+ @Override
+ public PeriodAndDuration read(JsonReader in) throws IOException {
+ String stringValue = in.nextString();
+ try {
+ return PeriodAndDuration.parse(stringValue);
+ } catch (DateTimeParseException ex) {
+ throw new JsonSyntaxException(
+ "Failed parsing '"
+ + stringValue
+ + "' as PeriodAndDuration; at path "
+ + in.getPreviousPath(),
+ ex);
+ }
+ }
+
+ @Override
+ public void write(JsonWriter out, PeriodAndDuration value) throws IOException {
+ out.value(value.toString());
+ }
+ };
+
+ private static final TypeAdapter BYTE_ARRAY =
+ new TypeAdapter() {
+ @Override
+ public byte[] read(JsonReader in) throws IOException {
+ String stringValue = in.nextString();
+ try {
+ if (stringValue.isEmpty()) {
+ return null;
+ }
+ return Base64.getDecoder().decode(stringValue);
+ } catch (IllegalArgumentException ex) {
+ throw new JsonSyntaxException(
+ "Failed parsing '"
+ + stringValue
+ + "' as byte[]; at path "
+ + in.getPreviousPath(),
+ ex);
+ }
+ }
+
+ @Override
+ public void write(JsonWriter out, byte[] value) throws IOException {
+ out.value(Base64.getEncoder().encodeToString(value));
+ }
+ };
+
+ private static final Gson defaultInstance = getDefaultBuilder().create();
+
+ public static @Nonnull Gson getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public static @Nonnull GsonBuilder getDefaultBuilder() {
+ return new GsonBuilder()
+ .registerTypeAdapter(OffsetDateTime.class, OFFSET_DATE_TIME.nullSafe())
+ .registerTypeAdapter(LocalDate.class, LOCAL_DATE.nullSafe())
+ .registerTypeAdapter(LocalTime.class, LOCAL_TIME.nullSafe())
+ .registerTypeAdapter(PeriodAndDuration.class, PERIOD_AND_DURATION.nullSafe())
+ .registerTypeAdapter(byte[].class, BYTE_ARRAY.nullSafe());
+ }
+}
diff --git a/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonParseNode.java b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonParseNode.java
index 8ee6e59a..f7b00659 100644
--- a/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonParseNode.java
+++ b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonParseNode.java
@@ -1,5 +1,6 @@
package com.microsoft.kiota.serialization;
+import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -11,13 +12,9 @@
import java.math.BigDecimal;
import java.time.LocalDate;
-import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
-import java.time.ZoneOffset;
-import java.time.format.DateTimeParseException;
import java.util.ArrayList;
-import java.util.Base64;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
@@ -31,13 +28,33 @@
/** ParseNode implementation for JSON */
public class JsonParseNode implements ParseNode {
private final JsonElement currentNode;
+ private final Gson gson;
/**
* Creates a new instance of the JsonParseNode class.
* @param node the node to wrap.
*/
public JsonParseNode(@Nonnull final JsonElement node) {
+ this(node, DefaultGsonBuilder.getDefaultInstance());
+ }
+
+ /**
+ * Creates a new instance of the JsonParseNode class.
+ * @param node the node to wrap.
+ * @param gson the Gson instance to use and deserialize the node with.
+ */
+ public JsonParseNode(@Nonnull final JsonElement node, @Nonnull final Gson gson) {
currentNode = Objects.requireNonNull(node, "parameter node cannot be null");
+ this.gson = Objects.requireNonNull(gson, "parameter gson cannot be null");
+ }
+
+ /**
+ * Creates a new {@link JsonParseNode} for the given {@link JsonElement}.
+ * @param node the node to wrap.
+ * @return the newly created {@link JsonParseNode}.
+ */
+ @Nonnull private JsonParseNode createNewNode(@Nonnull JsonElement node) {
+ return new JsonParseNode(node, gson);
}
/** {@inheritDoc} */
@@ -47,7 +64,7 @@ public JsonParseNode(@Nonnull final JsonElement node) {
final JsonObject object = currentNode.getAsJsonObject();
final JsonElement childNodeElement = object.get(identifier);
if (childNodeElement == null) return null;
- final JsonParseNode result = new JsonParseNode(childNodeElement);
+ final JsonParseNode result = createNewNode(childNodeElement);
result.setOnBeforeAssignFieldValues(this.onBeforeAssignFieldValues);
result.setOnAfterAssignFieldValues(this.onAfterAssignFieldValues);
return result;
@@ -55,114 +72,64 @@ public JsonParseNode(@Nonnull final JsonElement node) {
}
@Nullable public String getStringValue() {
- return currentNode.isJsonPrimitive() ? currentNode.getAsString() : null;
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, String.class) : null;
}
@Nullable public Boolean getBooleanValue() {
- return currentNode.isJsonPrimitive() ? currentNode.getAsBoolean() : null;
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, Boolean.class) : null;
}
@Nullable public Byte getByteValue() {
- return currentNode.isJsonPrimitive() ? currentNode.getAsByte() : null;
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, Byte.class) : null;
}
@Nullable public Short getShortValue() {
- return currentNode.isJsonPrimitive() ? currentNode.getAsShort() : null;
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, Short.class) : null;
}
@Nullable public BigDecimal getBigDecimalValue() {
- return currentNode.isJsonPrimitive() ? currentNode.getAsBigDecimal() : null;
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, BigDecimal.class) : null;
}
@Nullable public Integer getIntegerValue() {
- return currentNode.isJsonPrimitive() ? currentNode.getAsInt() : null;
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, Integer.class) : null;
}
@Nullable public Float getFloatValue() {
- return currentNode.isJsonPrimitive() ? currentNode.getAsFloat() : null;
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, Float.class) : null;
}
@Nullable public Double getDoubleValue() {
- return currentNode.isJsonPrimitive() ? currentNode.getAsDouble() : null;
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, Double.class) : null;
}
@Nullable public Long getLongValue() {
- return currentNode.isJsonPrimitive() ? currentNode.getAsLong() : null;
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, Long.class) : null;
}
@Nullable public UUID getUUIDValue() {
- final String stringValue = currentNode.getAsString();
- if (stringValue == null) return null;
- return UUID.fromString(stringValue);
+ return gson.fromJson(currentNode, UUID.class);
}
@Nullable public OffsetDateTime getOffsetDateTimeValue() {
- final String stringValue = currentNode.getAsString();
- if (stringValue == null) return null;
- try {
- return OffsetDateTime.parse(stringValue);
- } catch (DateTimeParseException ex) {
- // Append UTC offset if it's missing
- try {
- LocalDateTime localDateTime = LocalDateTime.parse(stringValue);
- return localDateTime.atOffset(ZoneOffset.UTC);
- } catch (DateTimeParseException ex2) {
- throw ex;
- }
- }
+ return gson.fromJson(currentNode, OffsetDateTime.class);
}
@Nullable public LocalDate getLocalDateValue() {
- final String stringValue = currentNode.getAsString();
- if (stringValue == null) return null;
- return LocalDate.parse(stringValue);
+ return gson.fromJson(currentNode, LocalDate.class);
}
@Nullable public LocalTime getLocalTimeValue() {
- final String stringValue = currentNode.getAsString();
- if (stringValue == null) return null;
- return LocalTime.parse(stringValue);
+ return gson.fromJson(currentNode, LocalTime.class);
}
@Nullable public PeriodAndDuration getPeriodAndDurationValue() {
- final String stringValue = currentNode.getAsString();
- if (stringValue == null) return null;
- return PeriodAndDuration.parse(stringValue);
+ return gson.fromJson(currentNode, PeriodAndDuration.class);
}
@Nullable private T getPrimitiveValue(
@Nonnull final Class targetClass, @Nonnull final JsonParseNode itemNode) {
- if (targetClass == Boolean.class) {
- return (T) itemNode.getBooleanValue();
- } else if (targetClass == Short.class) {
- return (T) itemNode.getShortValue();
- } else if (targetClass == Byte.class) {
- return (T) itemNode.getByteValue();
- } else if (targetClass == BigDecimal.class) {
- return (T) itemNode.getBigDecimalValue();
- } else if (targetClass == String.class) {
- return (T) itemNode.getStringValue();
- } else if (targetClass == Integer.class) {
- return (T) itemNode.getIntegerValue();
- } else if (targetClass == Float.class) {
- return (T) itemNode.getFloatValue();
- } else if (targetClass == Double.class) {
- return (T) itemNode.getDoubleValue();
- } else if (targetClass == Long.class) {
- return (T) itemNode.getLongValue();
- } else if (targetClass == UUID.class) {
- return (T) itemNode.getUUIDValue();
- } else if (targetClass == OffsetDateTime.class) {
- return (T) itemNode.getOffsetDateTimeValue();
- } else if (targetClass == LocalDate.class) {
- return (T) itemNode.getLocalDateValue();
- } else if (targetClass == LocalTime.class) {
- return (T) itemNode.getLocalTimeValue();
- } else if (targetClass == PeriodAndDuration.class) {
- return (T) itemNode.getPeriodAndDurationValue();
- } else {
- throw new RuntimeException("unknown type to deserialize " + targetClass.getName());
- }
+ return gson.fromJson(itemNode.currentNode, targetClass);
}
private List iterateOnArray(JsonElement jsonElement, Function fn) {
@@ -171,7 +138,7 @@ private List iterateOnArray(JsonElement jsonElement, Function result = new ArrayList<>();
while (sourceIterator.hasNext()) {
final JsonElement item = sourceIterator.next();
- final JsonParseNode itemNode = new JsonParseNode(item);
+ final JsonParseNode itemNode = createNewNode(item);
itemNode.setOnBeforeAssignFieldValues(this.getOnBeforeAssignFieldValues());
itemNode.setOnAfterAssignFieldValues(this.getOnAfterAssignFieldValues());
result.add(fn.apply(itemNode));
@@ -237,7 +204,7 @@ else if (element.isJsonPrimitive()) {
element.getAsJsonObject().entrySet()) {
final String fieldKey = fieldEntry.getKey();
final JsonElement fieldValue = fieldEntry.getValue();
- final JsonParseNode childNode = new JsonParseNode(fieldValue);
+ final JsonParseNode childNode = createNewNode(fieldValue);
childNode.setOnBeforeAssignFieldValues(this.getOnBeforeAssignFieldValues());
childNode.setOnAfterAssignFieldValues(this.getOnAfterAssignFieldValues());
propertiesMap.put(fieldKey, childNode.getUntypedValue());
@@ -294,7 +261,7 @@ private void assignFieldValues(
final JsonElement fieldValue = fieldEntry.getValue();
if (fieldValue.isJsonNull()) continue;
if (fieldDeserializer != null) {
- final JsonParseNode itemNode = new JsonParseNode(fieldValue);
+ final JsonParseNode itemNode = createNewNode(fieldValue);
itemNode.setOnBeforeAssignFieldValues(this.onBeforeAssignFieldValues);
itemNode.setOnAfterAssignFieldValues(this.onAfterAssignFieldValues);
fieldDeserializer.accept(itemNode);
@@ -344,10 +311,6 @@ public void setOnAfterAssignFieldValues(@Nullable final Consumer value
}
@Nullable public byte[] getByteArrayValue() {
- final String base64 = this.getStringValue();
- if (base64 == null || base64.isEmpty()) {
- return null;
- }
- return Base64.getDecoder().decode(base64);
+ return currentNode.isJsonPrimitive() ? gson.fromJson(currentNode, byte[].class) : null;
}
}
diff --git a/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonParseNodeFactory.java b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonParseNodeFactory.java
index c0c68eaa..65a8d487 100644
--- a/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonParseNodeFactory.java
+++ b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonParseNodeFactory.java
@@ -1,5 +1,6 @@
package com.microsoft.kiota.serialization;
+import com.google.gson.Gson;
import com.google.gson.JsonParser;
import jakarta.annotation.Nonnull;
@@ -12,8 +13,21 @@
/** Creates new Json parse nodes from the payload. */
public class JsonParseNodeFactory implements ParseNodeFactory {
+ private final Gson gson;
+
/** Creates a new factory */
- public JsonParseNodeFactory() {}
+ public JsonParseNodeFactory() {
+ this(DefaultGsonBuilder.getDefaultInstance());
+ }
+
+ /**
+ * Creates a new factory
+ * @param gson the {@link Gson} instance to use for parsing value types.
+ */
+ public JsonParseNodeFactory(@Nonnull Gson gson) {
+ Objects.requireNonNull(gson, "parameter gson cannot be null");
+ this.gson = gson;
+ }
/** {@inheritDoc} */
@Nonnull public String getValidContentType() {
@@ -35,7 +49,7 @@ public JsonParseNodeFactory() {}
}
try (final InputStreamReader reader =
new InputStreamReader(rawResponse, StandardCharsets.UTF_8)) {
- return new JsonParseNode(JsonParser.parseReader(reader));
+ return new JsonParseNode(JsonParser.parseReader(reader), gson);
} catch (IOException ex) {
throw new RuntimeException("could not close the reader", ex);
}
diff --git a/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriter.java b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriter.java
index f050d996..11ca51d7 100644
--- a/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriter.java
+++ b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriter.java
@@ -1,5 +1,6 @@
package com.microsoft.kiota.serialization;
+import com.google.gson.Gson;
import com.google.gson.stream.JsonWriter;
import com.microsoft.kiota.PeriodAndDuration;
@@ -17,8 +18,6 @@
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.OffsetDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.Base64;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
@@ -34,10 +33,22 @@
public class JsonSerializationWriter implements SerializationWriter {
private final ByteArrayOutputStream stream = new ByteArrayOutputStream();
private final JsonWriter writer;
+ private final Gson gson;
- /** Creates a new instance of a json serialization writer */
+ /**
+ * Creates a new instance of a json serialization writer
+ */
public JsonSerializationWriter() {
+ this(DefaultGsonBuilder.getDefaultInstance());
+ }
+
+ /**
+ * Creates a new instance of a json serialization writer
+ * @param gson the {@link Gson} instance to use for writing value types.
+ */
+ public JsonSerializationWriter(@Nonnull Gson gson) {
this.writer = new JsonWriter(new OutputStreamWriter(this.stream, StandardCharsets.UTF_8));
+ this.gson = Objects.requireNonNull(gson, "parameter gson cannot be null");
}
public void writeStringValue(@Nullable final String key, @Nullable final String value) {
@@ -46,7 +57,7 @@ public void writeStringValue(@Nullable final String key, @Nullable final String
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value);
+ gson.getAdapter(String.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -58,7 +69,7 @@ public void writeBooleanValue(@Nullable final String key, @Nullable final Boolea
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value);
+ gson.getAdapter(Boolean.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -70,7 +81,7 @@ public void writeShortValue(@Nullable final String key, @Nullable final Short va
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value);
+ gson.getAdapter(Short.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -82,7 +93,7 @@ public void writeByteValue(@Nullable final String key, @Nullable final Byte valu
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value);
+ gson.getAdapter(Byte.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -94,7 +105,7 @@ public void writeBigDecimalValue(@Nullable final String key, @Nullable final Big
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value);
+ gson.getAdapter(BigDecimal.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -106,7 +117,7 @@ public void writeIntegerValue(@Nullable final String key, @Nullable final Intege
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value);
+ gson.getAdapter(Integer.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -118,7 +129,7 @@ public void writeFloatValue(@Nullable final String key, @Nullable final Float va
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value);
+ gson.getAdapter(Float.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -130,7 +141,7 @@ public void writeDoubleValue(@Nullable final String key, @Nullable final Double
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value);
+ gson.getAdapter(Double.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -142,7 +153,7 @@ public void writeLongValue(@Nullable final String key, @Nullable final Long valu
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value);
+ gson.getAdapter(Long.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -154,7 +165,7 @@ public void writeUUIDValue(@Nullable final String key, @Nullable final UUID valu
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value.toString());
+ gson.getAdapter(UUID.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -167,7 +178,7 @@ public void writeOffsetDateTimeValue(
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
+ gson.getAdapter(OffsetDateTime.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -179,7 +190,7 @@ public void writeLocalDateValue(@Nullable final String key, @Nullable final Loca
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
+ gson.getAdapter(LocalDate.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -191,7 +202,7 @@ public void writeLocalTimeValue(@Nullable final String key, @Nullable final Loca
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value.format(DateTimeFormatter.ISO_LOCAL_TIME));
+ gson.getAdapter(LocalTime.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -204,7 +215,7 @@ public void writePeriodAndDurationValue(
if (key != null && !key.isEmpty()) {
writer.name(key);
}
- writer.value(value.toString());
+ gson.getAdapter(PeriodAndDuration.class).write(writer, value);
} catch (IOException ex) {
throw new RuntimeException("could not serialize value", ex);
}
@@ -528,6 +539,14 @@ public void setOnStartObjectSerialization(
}
public void writeByteArrayValue(@Nullable final String key, @Nullable final byte[] value) {
- if (value != null) this.writeStringValue(key, Base64.getEncoder().encodeToString(value));
+ if (value != null)
+ try {
+ if (key != null && !key.isEmpty()) {
+ writer.name(key);
+ }
+ gson.getAdapter(byte[].class).write(writer, value);
+ } catch (IOException ex) {
+ throw new RuntimeException("could not serialize value", ex);
+ }
}
}
diff --git a/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriterFactory.java b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriterFactory.java
index c72d01d0..f6420a1c 100644
--- a/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriterFactory.java
+++ b/components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriterFactory.java
@@ -1,13 +1,28 @@
package com.microsoft.kiota.serialization;
+import com.google.gson.Gson;
+
import jakarta.annotation.Nonnull;
import java.util.Objects;
/** Creates new Json serialization writers. */
public class JsonSerializationWriterFactory implements SerializationWriterFactory {
+ private final Gson gson;
+
/** Creates a new factory */
- public JsonSerializationWriterFactory() {}
+ public JsonSerializationWriterFactory() {
+ this(DefaultGsonBuilder.getDefaultInstance());
+ }
+
+ /**
+ * Creates a new factory
+ * @param gson the {@link Gson} instance to use for writing value types.
+ */
+ public JsonSerializationWriterFactory(@Nonnull Gson gson) {
+ Objects.requireNonNull(gson, "gson contentType cannot be null");
+ this.gson = gson;
+ }
/** {@inheritDoc} */
@Nonnull public String getValidContentType() {
@@ -25,6 +40,6 @@ public JsonSerializationWriterFactory() {}
} else if (!contentType.equals(validContentType)) {
throw new IllegalArgumentException("expected a " + validContentType + " content type");
}
- return new JsonSerializationWriter();
+ return new JsonSerializationWriter(gson);
}
}
diff --git a/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/JsonParseNodeTests.java b/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/JsonParseNodeTests.java
index ec31bd0e..8447f083 100644
--- a/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/JsonParseNodeTests.java
+++ b/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/JsonParseNodeTests.java
@@ -1,8 +1,17 @@
package com.microsoft.kiota.serialization;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import com.google.gson.Gson;
import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
import com.microsoft.kiota.serialization.mocks.MyEnum;
import com.microsoft.kiota.serialization.mocks.TestEntity;
import com.microsoft.kiota.serialization.mocks.UntypedTestEntity;
@@ -12,7 +21,11 @@
import org.junit.jupiter.params.provider.ValueSource;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
class JsonParseNodeTests {
@@ -66,6 +79,44 @@ class JsonParseNodeTests {
+ " }\r\n"
+ "}";
+ public static final DateTimeFormatter customFormatter =
+ new DateTimeFormatterBuilder()
+ .parseCaseInsensitive()
+ .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
+ .parseLenient()
+ .appendOffset("+HHmm", "+0000")
+ .parseStrict()
+ .toFormatter();
+
+ public static final Gson customGson =
+ DefaultGsonBuilder.getDefaultBuilder()
+ .registerTypeAdapter(
+ OffsetDateTime.class,
+ new TypeAdapter() {
+ @Override
+ public OffsetDateTime read(JsonReader in) throws IOException {
+ String stringValue = in.nextString();
+ try {
+ return customFormatter.parse(
+ stringValue, OffsetDateTime::from);
+ } catch (DateTimeParseException ex) {
+ throw new JsonSyntaxException(
+ "Failed parsing '"
+ + stringValue
+ + "' as LocalDate; at path "
+ + in.getPreviousPath(),
+ ex);
+ }
+ }
+
+ @Override
+ public void write(JsonWriter out, OffsetDateTime value)
+ throws IOException {
+ out.value(customFormatter.format(value));
+ }
+ }.nullSafe())
+ .create();
+
@Test
void itDDoesNotFailForGetChildElementOnMissingKey() throws UnsupportedEncodingException {
final var initialString = "{displayName\": \"Microsoft Teams Meeting\"}";
@@ -79,7 +130,9 @@ void itDDoesNotFailForGetChildElementOnMissingKey() throws UnsupportedEncodingEx
void testParsesDateTimeOffset() {
final var dateTimeOffsetString = "2024-02-12T19:47:39+02:00";
final var jsonElement = JsonParser.parseString("\"" + dateTimeOffsetString + "\"");
- final var result = new JsonParseNode(jsonElement).getOffsetDateTimeValue();
+ final var result =
+ new JsonParseNode(jsonElement, DefaultGsonBuilder.getDefaultInstance())
+ .getOffsetDateTimeValue();
assertEquals(dateTimeOffsetString, result.toString());
}
@@ -87,7 +140,9 @@ void testParsesDateTimeOffset() {
void testParsesDateTimeStringWithoutOffsetToDateTimeOffset() {
final var dateTimeString = "2024-02-12T19:47:39";
final var jsonElement = JsonParser.parseString("\"" + dateTimeString + "\"");
- final var result = new JsonParseNode(jsonElement).getOffsetDateTimeValue();
+ final var result =
+ new JsonParseNode(jsonElement, DefaultGsonBuilder.getDefaultInstance())
+ .getOffsetDateTimeValue();
assertEquals(dateTimeString + "Z", result.toString());
}
@@ -96,13 +151,23 @@ void testParsesDateTimeStringWithoutOffsetToDateTimeOffset() {
void testInvalidOffsetDateTimeStringThrowsException(final String dateTimeString) {
final var jsonElement = JsonParser.parseString("\"" + dateTimeString + "\"");
try {
- new JsonParseNode(jsonElement).getOffsetDateTimeValue();
+ new JsonParseNode(jsonElement, DefaultGsonBuilder.getDefaultInstance())
+ .getOffsetDateTimeValue();
} catch (final Exception ex) {
- assertInstanceOf(DateTimeParseException.class, ex);
+ assertInstanceOf(JsonSyntaxException.class, ex);
assertTrue(ex.getMessage().contains(dateTimeString));
}
}
+ @Test
+ void testNonStandardOffsetDateTimeParsing() {
+ final var dateTimeString = "2024-02-12T19:47:39+0000";
+ final var jsonElement = JsonParser.parseString("\"" + dateTimeString + "\"");
+ final var parsedOffsetDateTime =
+ new JsonParseNode(jsonElement, customGson).getOffsetDateTimeValue();
+ assertEquals(OffsetDateTime.parse("2024-02-12T19:47:39+00:00"), parsedOffsetDateTime);
+ }
+
@Test
void getEntityWithArrayInAdditionalData() throws UnsupportedEncodingException {
final var rawResponse = new ByteArrayInputStream(testJsonString.getBytes("UTF-8"));
diff --git a/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/JsonSerializationWriterTests.java b/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/JsonSerializationWriterTests.java
index f9a5f503..9bdac251 100644
--- a/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/JsonSerializationWriterTests.java
+++ b/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/JsonSerializationWriterTests.java
@@ -1,8 +1,11 @@
package com.microsoft.kiota.serialization;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
import com.microsoft.kiota.Compatibility;
+import com.microsoft.kiota.PeriodAndDuration;
import com.microsoft.kiota.serialization.mocks.MyEnum;
import com.microsoft.kiota.serialization.mocks.TestEntity;
import com.microsoft.kiota.serialization.mocks.UntypedTestEntity;
@@ -10,10 +13,17 @@
import org.junit.jupiter.api.Test;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.Period;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.UUID;
class JsonSerializationWriterTests {
@@ -53,7 +63,8 @@ void writesSampleObjectValueWithPrimitivesInAdditionalData() throws IOException
.getAdditionalData()
.put("aliases", aliases); // place a collection in the additional data
- try (final var jsonSerializer = new JsonSerializationWriter()) {
+ try (final var jsonSerializer =
+ new JsonSerializationWriter(DefaultGsonBuilder.getDefaultInstance())) {
jsonSerializer.writeObjectValue("", testEntity);
var contentStream = jsonSerializer.getSerializedContent();
var serializedJsonString =
@@ -66,6 +77,22 @@ void writesSampleObjectValueWithPrimitivesInAdditionalData() throws IOException
}
}
+ @Test
+ void useNonStandardOffsetDateTimeFormat() throws IOException {
+ var testEntity = new TestEntity();
+ testEntity.setCreatedDateTime(OffsetDateTime.parse("2024-02-12T19:47:39+00:00"));
+ try (final var jsonSerializer =
+ new JsonSerializationWriter(JsonParseNodeTests.customGson)) {
+ jsonSerializer.writeObjectValue("", testEntity);
+ var contentStream = jsonSerializer.getSerializedContent();
+ var serializedJsonString =
+ new String(Compatibility.readAllBytes(contentStream), "UTF-8");
+ // Assert
+ var expectedString = "{\"createdDateTime\":\"2024-02-12T19:47:39+0000\"}";
+ assertEquals(expectedString, serializedJsonString);
+ }
+ }
+
@Test
void writesSampleObjectValueWithParsableInAdditionalData() throws IOException {
var testEntity = new TestEntity();
@@ -81,7 +108,8 @@ void writesSampleObjectValueWithParsableInAdditionalData() throws IOException {
.getAdditionalData()
.put("manager", managerAdditionalData); // place a parsable in the addtionaldata
- try (final var jsonSerializer = new JsonSerializationWriter()) {
+ try (final var jsonSerializer =
+ new JsonSerializationWriter(DefaultGsonBuilder.getDefaultInstance())) {
jsonSerializer.writeObjectValue("", testEntity);
var contentStream = jsonSerializer.getSerializedContent();
var serializedJsonString =
@@ -186,7 +214,8 @@ void writesSampleObjectValueWithUntypedProperties() throws IOException {
}
}));
- try (final var jsonSerializer = new JsonSerializationWriter()) {
+ try (final var jsonSerializer =
+ new JsonSerializationWriter(DefaultGsonBuilder.getDefaultInstance())) {
jsonSerializer.writeObjectValue("", untypedTestEntity);
var contentStream = jsonSerializer.getSerializedContent();
var serializedJsonString =
@@ -205,4 +234,66 @@ void writesSampleObjectValueWithUntypedProperties() throws IOException {
assertEquals(expectedString, serializedJsonString);
}
}
+
+ @Test
+ void parseWrittenValues() throws IOException {
+ writeAndParse(
+ ParseNode::getStringValue, SerializationWriter::writeStringValue, "just a string");
+ writeAndParse(ParseNode::getBooleanValue, SerializationWriter::writeBooleanValue, true);
+ writeAndParse(ParseNode::getByteValue, SerializationWriter::writeByteValue, (byte) 3);
+ writeAndParse(ParseNode::getShortValue, SerializationWriter::writeShortValue, (short) 42);
+ writeAndParse(
+ ParseNode::getBigDecimalValue,
+ SerializationWriter::writeBigDecimalValue,
+ new BigDecimal(123456789L));
+ writeAndParse(ParseNode::getIntegerValue, SerializationWriter::writeIntegerValue, 54321);
+ writeAndParse(
+ ParseNode::getFloatValue, SerializationWriter::writeFloatValue, (float) 67.89);
+ writeAndParse(ParseNode::getDoubleValue, SerializationWriter::writeDoubleValue, 3245.12356);
+ writeAndParse(ParseNode::getLongValue, SerializationWriter::writeLongValue, -543219876L);
+ writeAndParse(
+ ParseNode::getUUIDValue, SerializationWriter::writeUUIDValue, UUID.randomUUID());
+ writeAndParse(
+ ParseNode::getOffsetDateTimeValue,
+ SerializationWriter::writeOffsetDateTimeValue,
+ OffsetDateTime.now());
+ writeAndParse(
+ ParseNode::getLocalDateValue,
+ SerializationWriter::writeLocalDateValue,
+ LocalDate.now());
+ writeAndParse(
+ ParseNode::getLocalTimeValue,
+ SerializationWriter::writeLocalTimeValue,
+ LocalTime.now());
+ writeAndParse(
+ ParseNode::getPeriodAndDurationValue,
+ SerializationWriter::writePeriodAndDurationValue,
+ PeriodAndDuration.of(Period.ofYears(3), Duration.ofHours(6)));
+ writeAndParse(
+ ParseNode::getByteArrayValue,
+ SerializationWriter::writeByteArrayValue,
+ new byte[] {8, 10, 127, -50, 0});
+ }
+
+ private void writeAndParse(
+ TestParsable.ParseMethod parseMethod,
+ TestParsable.WriteMethod writeMethod,
+ T value)
+ throws IOException {
+ var testParsable = new TestParsable<>(parseMethod, writeMethod, value);
+ var writer = new JsonSerializationWriter(DefaultGsonBuilder.getDefaultInstance());
+ writer.writeObjectValue(null, testParsable);
+
+ var parseNodeFactory = new JsonParseNodeFactory(DefaultGsonBuilder.getDefaultInstance());
+ var parseNode =
+ parseNodeFactory.getParseNode("application/json", writer.getSerializedContent());
+ var result = parseNode.getObjectValue(TestParsable.factory(parseMethod, writeMethod));
+ if (value instanceof byte[] bytea) {
+ assertArrayEquals(bytea, (byte[]) result.getRealValue());
+ } else {
+ assertEquals(value, result.getRealValue());
+ }
+ assertNull(result.getNullValue());
+ writer.close();
+ }
}
diff --git a/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/TestParsable.java b/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/TestParsable.java
new file mode 100644
index 00000000..c0dd35fb
--- /dev/null
+++ b/components/serialization/json/src/test/java/com/microsoft/kiota/serialization/TestParsable.java
@@ -0,0 +1,71 @@
+package com.microsoft.kiota.serialization;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class TestParsable implements Parsable {
+ @FunctionalInterface
+ public interface WriteMethod {
+ public void write(SerializationWriter writer, String key, V value);
+ }
+
+ @FunctionalInterface
+ public interface ParseMethod {
+ public V parse(ParseNode parseNode);
+ }
+
+ private T realValue;
+
+ private T nullValue;
+
+ private final ParseMethod parseMethod;
+
+ private final WriteMethod writeMethod;
+
+ public TestParsable(ParseMethod parseMethod, WriteMethod writeMethod) {
+ this(parseMethod, writeMethod, null);
+ }
+
+ public TestParsable(ParseMethod parseMethod, WriteMethod writeMethod, T value) {
+ this.parseMethod = parseMethod;
+ this.writeMethod = writeMethod;
+ this.realValue = value;
+ }
+
+ @Override
+ public Map> getFieldDeserializers() {
+ final HashMap> deserializerMap =
+ new HashMap>(2);
+ deserializerMap.put(
+ "realValue",
+ (n) -> {
+ this.realValue = parseMethod.parse(n);
+ });
+ deserializerMap.put(
+ "nullValue",
+ (n) -> {
+ this.nullValue = parseMethod.parse(n);
+ });
+ return deserializerMap;
+ }
+
+ @Override
+ public void serialize(SerializationWriter writer) {
+ writeMethod.write(writer, "realValue", realValue);
+ writeMethod.write(writer, "nullValue", nullValue);
+ }
+
+ public T getRealValue() {
+ return realValue;
+ }
+
+ public T getNullValue() {
+ return nullValue;
+ }
+
+ public static ParsableFactory> factory(
+ ParseMethod parseMethod, WriteMethod writeMethod) {
+ return (n) -> new TestParsable(parseMethod, writeMethod);
+ }
+}