Skip to content

Commit

Permalink
Merge pull request #839 from andreaTP/perf-and-simplifications
Browse files Browse the repository at this point in the history
Json parse improvements
  • Loading branch information
baywet authored Nov 20, 2023
2 parents 033afd1 + 651a46f commit c59eb4a
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 162 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Added Spotless as an automatic formatting tool for the entire codebase
- Changed some internal implementations of JsonParse for performance and readability reasons

## [0.9.2] - 2023-11-16

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;

/** ParseNode implementation for JSON */
public class JsonParseNode implements ParseNode {
Expand Down Expand Up @@ -116,80 +117,59 @@ public JsonParseNode(@Nonnull final JsonElement node) {
return PeriodAndDuration.parse(stringValue);
}

@Nullable private <T> T getPrimitiveValue(
@Nonnull final Class<T> 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 == 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());
}
}

private <T> List<T> iterateOnArray(Function<JsonParseNode, T> fn) {
JsonArray array = currentNode.getAsJsonArray();
final Iterator<JsonElement> sourceIterator = array.iterator();
final List<T> result = new ArrayList<>();
while (sourceIterator.hasNext()) {
final JsonElement item = sourceIterator.next();
final JsonParseNode itemNode = new JsonParseNode(item);
itemNode.setOnBeforeAssignFieldValues(this.getOnBeforeAssignFieldValues());
itemNode.setOnAfterAssignFieldValues(this.getOnAfterAssignFieldValues());
result.add(fn.apply(itemNode));
}
return result;
}

@Nullable public <T> List<T> getCollectionOfPrimitiveValues(@Nonnull final Class<T> targetClass) {
Objects.requireNonNull(targetClass, "parameter targetClass cannot be null");
if (currentNode.isJsonNull()) {
return null;
} else if (currentNode.isJsonArray()) {
final JsonArray array = currentNode.getAsJsonArray();
final Iterator<JsonElement> sourceIterator = array.iterator();
final JsonParseNode _this = this;
final List<T> result = new ArrayList<>();
final Iterable<T> iterable =
new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
@Override
public boolean hasNext() {
return sourceIterator.hasNext();
}

@Override
@SuppressWarnings("unchecked")
public T next() {
final JsonElement item = sourceIterator.next();
final Consumer<Parsable> onBefore =
_this.getOnBeforeAssignFieldValues();
final Consumer<Parsable> onAfter =
_this.getOnAfterAssignFieldValues();
final JsonParseNode itemNode =
new JsonParseNode(item) {
{
this.setOnBeforeAssignFieldValues(onBefore);
this.setOnAfterAssignFieldValues(onAfter);
}
};
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 == 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());
}
}
};
}
};

for (T elem : iterable) {
result.add(elem);
}
return result;
return iterateOnArray(itemNode -> getPrimitiveValue(targetClass, itemNode));
} else throw new RuntimeException("invalid state expected to have an array node");
}

Expand All @@ -199,44 +179,7 @@ public T next() {
if (currentNode.isJsonNull()) {
return null;
} else if (currentNode.isJsonArray()) {
final JsonArray array = currentNode.getAsJsonArray();
final Iterator<JsonElement> sourceIterator = array.iterator();
final JsonParseNode _this = this;
final List<T> result = new ArrayList<>();
final Iterable<T> iterable =
new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
@Override
public boolean hasNext() {
return sourceIterator.hasNext();
}

@Override
public T next() {
final JsonElement item = sourceIterator.next();
final Consumer<Parsable> onBefore =
_this.getOnBeforeAssignFieldValues();
final Consumer<Parsable> onAfter =
_this.getOnAfterAssignFieldValues();
final JsonParseNode itemNode =
new JsonParseNode(item) {
{
this.setOnBeforeAssignFieldValues(onBefore);
this.setOnAfterAssignFieldValues(onAfter);
}
};
return itemNode.getObjectValue(factory);
}
};
}
};

for (T elem : iterable) {
result.add(elem);
}
return result;
return iterateOnArray(itemNode -> itemNode.getObjectValue(factory));
} else return null;
}

Expand All @@ -246,44 +189,7 @@ public T next() {
if (currentNode.isJsonNull()) {
return null;
} else if (currentNode.isJsonArray()) {
final JsonArray array = currentNode.getAsJsonArray();
final Iterator<JsonElement> sourceIterator = array.iterator();
final JsonParseNode _this = this;
final List<T> result = new ArrayList<>();
final Iterable<T> iterable =
new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
@Override
public boolean hasNext() {
return sourceIterator.hasNext();
}

@Override
public T next() {
final JsonElement item = sourceIterator.next();
final Consumer<Parsable> onBefore =
_this.getOnBeforeAssignFieldValues();
final Consumer<Parsable> onAfter =
_this.getOnAfterAssignFieldValues();
final JsonParseNode itemNode =
new JsonParseNode(item) {
{
this.setOnBeforeAssignFieldValues(onBefore);
this.setOnAfterAssignFieldValues(onAfter);
}
};
return itemNode.getEnumValue(targetEnum);
}
};
}
};

for (T elem : iterable) {
result.add(elem);
}
return result;
return iterateOnArray(itemNode -> itemNode.getEnumValue(targetEnum));
} else throw new RuntimeException("invalid state expected to have an array node");
}

Expand Down Expand Up @@ -348,15 +254,10 @@ private <T extends Parsable> void assignFieldValues(
final JsonElement fieldValue = fieldEntry.getValue();
if (fieldValue.isJsonNull()) continue;
if (fieldDeserializer != null) {
final Consumer<Parsable> onBefore = this.onBeforeAssignFieldValues;
final Consumer<Parsable> onAfter = this.onAfterAssignFieldValues;
fieldDeserializer.accept(
new JsonParseNode(fieldValue) {
{
this.setOnBeforeAssignFieldValues(onBefore);
this.setOnAfterAssignFieldValues(onAfter);
}
});
final JsonParseNode itemNode = new JsonParseNode(fieldValue);
itemNode.setOnBeforeAssignFieldValues(this.onBeforeAssignFieldValues);
itemNode.setOnAfterAssignFieldValues(this.onAfterAssignFieldValues);
fieldDeserializer.accept(itemNode);
} else if (itemAdditionalData != null)
itemAdditionalData.put(fieldKey, this.tryGetAnything(fieldValue));
}
Expand All @@ -367,16 +268,16 @@ private <T extends Parsable> void assignFieldValues(
}

private Object tryGetAnything(final JsonElement element) {
if (element.isJsonPrimitive()) {
if (element.isJsonNull()) return null;
else if (element.isJsonPrimitive()) {
final JsonPrimitive primitive = element.getAsJsonPrimitive();
if (primitive.isBoolean()) return primitive.getAsBoolean();
else if (primitive.isString()) return primitive.getAsString();
else if (primitive.isNumber()) return primitive.getAsFloat();
else if (primitive.isNumber()) return primitive.getAsDouble();
else
throw new RuntimeException(
"Could not get the value during deserialization, unknown primitive type");
} else if (element.isJsonNull()) return null;
else if (element.isJsonObject() || element.isJsonArray()) return element;
} else if (element.isJsonObject() || element.isJsonArray()) return element;
else
throw new RuntimeException(
"Could not get the value during deserialization, unknown primitive type");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.junit.jupiter.api.Assertions.*;

import com.microsoft.kiota.serialization.mocks.IntersectionTypeMock;
import com.microsoft.kiota.serialization.mocks.MyEnum;
import com.microsoft.kiota.serialization.mocks.SecondTestEntity;
import com.microsoft.kiota.serialization.mocks.TestEntity;
import java.io.ByteArrayInputStream;
Expand Down Expand Up @@ -64,9 +65,9 @@ void ParsesIntersectionTypeComplexProperty2() throws UnsupportedEncodingExceptio
void ParsesIntersectionTypeComplexProperty3() throws UnsupportedEncodingException {
final var initialString =
"[{\"@odata.type\":\"#microsoft.graph.TestEntity\",\"officeLocation\":\"Ottawa\","
+ " \"id\": \"11\"},"
+ " \"id\": \"11\",\"myEnum\": \"VALUE2\"},"
+ " {\"@odata.type\":\"#microsoft.graph.TestEntity\",\"officeLocation\":\"Montreal\","
+ " \"id\": \"10\"}]";
+ " \"id\": \"10\",\"myEnum\": \"VALUE1\"}]";
final var rawResponse = new ByteArrayInputStream(initialString.getBytes("UTF-8"));
final var parseNode = _parseNodeFactory.getParseNode(contentType, rawResponse);
final var result =
Expand All @@ -78,6 +79,8 @@ void ParsesIntersectionTypeComplexProperty3() throws UnsupportedEncodingExceptio
assertNull(result.getStringValue());
assertEquals(2, result.getComposedType3().size());
assertEquals("Ottawa", result.getComposedType3().get(0).getOfficeLocation());
assertEquals(MyEnum.MY_VALUE2, result.getComposedType3().get(0).getMyEnum());
assertEquals(MyEnum.MY_VALUE1, result.getComposedType3().get(1).getMyEnum());
}

@Test
Expand Down Expand Up @@ -183,13 +186,15 @@ void SerializesIntersectionTypeComplexProperty3() throws IOException {
{
setOfficeLocation("Montreal");
setId("10");
setMyEnum(MyEnum.MY_VALUE2);
}
});
add(
new TestEntity() {
{
setOfficeLocation("Ottawa");
setId("11");
setMyEnum(MyEnum.MY_VALUE1);
}
});
}
Expand All @@ -201,7 +206,7 @@ void SerializesIntersectionTypeComplexProperty3() throws IOException {
try (final var result = writer.getSerializedContent()) {
final String text = new String(result.readAllBytes(), StandardCharsets.UTF_8);
assertEquals(
"[{\"id\":\"10\",\"officeLocation\":\"Montreal\"},{\"id\":\"11\",\"officeLocation\":\"Ottawa\"}]",
"[{\"id\":\"10\",\"officeLocation\":\"Montreal\",\"myEnum\":\"VALUE2\"},{\"id\":\"11\",\"officeLocation\":\"Ottawa\",\"myEnum\":\"VALUE1\"}]",
text);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.microsoft.kiota.serialization.mocks;

import com.microsoft.kiota.serialization.ValuedEnum;
import java.util.Objects;

public enum MyEnum implements ValuedEnum {
MY_VALUE1("VALUE1"),
MY_VALUE2("VALUE2");
public final String value;

MyEnum(final String value) {
this.value = value;
}

@jakarta.annotation.Nonnull public String getValue() {
return this.value;
}

@jakarta.annotation.Nullable public static MyEnum forValue(@jakarta.annotation.Nonnull final String searchValue) {
Objects.requireNonNull(searchValue);
switch (searchValue) {
case "VALUE1":
return MY_VALUE1;
case "VALUE2":
return MY_VALUE2;
default:
return null;
}
}
}
Loading

0 comments on commit c59eb4a

Please sign in to comment.