Skip to content

Commit 795b5e5

Browse files
Copilotbaywet
andcommitted
perf: reduce parse node allocations when deserializing primitive types
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent 374788c commit 795b5e5

2 files changed

Lines changed: 175 additions & 84 deletions

File tree

components/serialization/form/src/main/java/com/microsoft/kiota/serialization/FormParseNode.java

Lines changed: 147 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@
1616
import java.time.ZoneOffset;
1717
import java.time.format.DateTimeParseException;
1818
import java.util.ArrayList;
19-
import java.util.Arrays;
2019
import java.util.Base64;
2120
import java.util.EnumSet;
2221
import java.util.HashMap;
23-
import java.util.Iterator;
2422
import java.util.List;
2523
import java.util.Locale;
2624
import java.util.Map;
@@ -202,76 +200,157 @@ private String sanitizeKey(@Nonnull final String key) {
202200
}
203201

204202
@Nullable public <T> List<T> getCollectionOfPrimitiveValues(@Nonnull final Class<T> targetClass) {
205-
final List<String> primitiveStringCollection = Arrays.asList(getStringValue().split(","));
206-
final Iterator<String> sourceIterator = primitiveStringCollection.iterator();
207-
final FormParseNode _this = this;
208-
final List<T> result = new ArrayList<>();
209-
final Iterable<T> iterable =
210-
new Iterable<T>() {
211-
@Override
212-
public Iterator<T> iterator() {
213-
return new Iterator<T>() {
214-
@Override
215-
public boolean hasNext() {
216-
return sourceIterator.hasNext();
217-
}
218-
219-
@Override
220-
@SuppressWarnings("unchecked")
221-
public T next() {
222-
final String item = sourceIterator.next();
223-
final Consumer<Parsable> onBefore =
224-
_this.getOnBeforeAssignFieldValues();
225-
final Consumer<Parsable> onAfter =
226-
_this.getOnAfterAssignFieldValues();
227-
final FormParseNode itemNode =
228-
new FormParseNode(item) {
229-
{
230-
this.setOnBeforeAssignFieldValues(onBefore);
231-
this.setOnAfterAssignFieldValues(onAfter);
232-
}
233-
};
234-
if (targetClass == Boolean.class) {
235-
return (T) itemNode.getBooleanValue();
236-
} else if (targetClass == Short.class) {
237-
return (T) itemNode.getShortValue();
238-
} else if (targetClass == Byte.class) {
239-
return (T) itemNode.getByteValue();
240-
} else if (targetClass == BigDecimal.class) {
241-
return (T) itemNode.getBigDecimalValue();
242-
} else if (targetClass == String.class) {
243-
return (T) itemNode.getStringValue();
244-
} else if (targetClass == Integer.class) {
245-
return (T) itemNode.getIntegerValue();
246-
} else if (targetClass == Float.class) {
247-
return (T) itemNode.getFloatValue();
248-
} else if (targetClass == Long.class) {
249-
return (T) itemNode.getLongValue();
250-
} else if (targetClass == UUID.class) {
251-
return (T) itemNode.getUUIDValue();
252-
} else if (targetClass == OffsetDateTime.class) {
253-
return (T) itemNode.getOffsetDateTimeValue();
254-
} else if (targetClass == LocalDate.class) {
255-
return (T) itemNode.getLocalDateValue();
256-
} else if (targetClass == LocalTime.class) {
257-
return (T) itemNode.getLocalTimeValue();
258-
} else if (targetClass == PeriodAndDuration.class) {
259-
return (T) itemNode.getPeriodAndDurationValue();
260-
} else {
261-
throw new RuntimeException(
262-
"unknown type to deserialize " + targetClass.getName());
263-
}
264-
}
265-
};
266-
}
267-
};
268-
269-
for (T elem : iterable) {
270-
result.add(elem);
203+
final String[] primitiveStringCollection = getStringValue().split(",");
204+
final List<T> result = new ArrayList<>(primitiveStringCollection.length);
205+
for (final String item : primitiveStringCollection) {
206+
String decodedItem;
207+
try {
208+
decodedItem = URLDecoder.decode(item, encoding);
209+
} catch (UnsupportedEncodingException e) {
210+
decodedItem = item;
211+
}
212+
result.add(convertPrimitiveValue(decodedItem, targetClass));
271213
}
272214
return result;
273215
}
274216

217+
@SuppressWarnings("unchecked")
218+
@Nonnull private static <T> T convertPrimitiveValue(
219+
@Nullable final String value, @Nonnull final Class<T> targetClass) {
220+
if (targetClass == Boolean.class) {
221+
return (T) parseBooleanValue(value);
222+
} else if (targetClass == Short.class) {
223+
return (T) parseShortValue(value);
224+
} else if (targetClass == Byte.class) {
225+
return (T) parseByteValue(value);
226+
} else if (targetClass == BigDecimal.class) {
227+
return (T) parseBigDecimalValue(value);
228+
} else if (targetClass == String.class) {
229+
return (T) (value != null && value.equalsIgnoreCase("null") ? null : value);
230+
} else if (targetClass == Integer.class) {
231+
return (T) parseIntegerValue(value);
232+
} else if (targetClass == Float.class) {
233+
return (T) parseFloatValue(value);
234+
} else if (targetClass == Long.class) {
235+
return (T) parseLongValue(value);
236+
} else if (targetClass == UUID.class) {
237+
return (T) parseUUIDValue(value);
238+
} else if (targetClass == OffsetDateTime.class) {
239+
return (T) parseOffsetDateTimeValue(value);
240+
} else if (targetClass == LocalDate.class) {
241+
return (T) parseLocalDateValue(value);
242+
} else if (targetClass == LocalTime.class) {
243+
return (T) parseLocalTimeValue(value);
244+
} else if (targetClass == PeriodAndDuration.class) {
245+
return (T) parsePeriodAndDurationValue(value);
246+
} else {
247+
throw new RuntimeException("unknown type to deserialize " + targetClass.getName());
248+
}
249+
}
250+
251+
@Nullable private static Boolean parseBooleanValue(@Nullable final String value) {
252+
if (value == null) return null;
253+
switch (value.toLowerCase(Locale.ROOT)) {
254+
case "true":
255+
case "1":
256+
return true;
257+
case "false":
258+
case "0":
259+
return false;
260+
default:
261+
return null;
262+
}
263+
}
264+
265+
@Nullable private static Byte parseByteValue(@Nullable final String value) {
266+
if (value == null) return null;
267+
try {
268+
return Byte.parseByte(value);
269+
} catch (final NumberFormatException ex) {
270+
return null;
271+
}
272+
}
273+
274+
@Nullable private static Short parseShortValue(@Nullable final String value) {
275+
if (value == null) return null;
276+
try {
277+
return Short.parseShort(value);
278+
} catch (final NumberFormatException ex) {
279+
return null;
280+
}
281+
}
282+
283+
@Nullable private static BigDecimal parseBigDecimalValue(@Nullable final String value) {
284+
if (value == null) return null;
285+
try {
286+
return new BigDecimal(value);
287+
} catch (final NumberFormatException ex) {
288+
return null;
289+
}
290+
}
291+
292+
@Nullable private static Integer parseIntegerValue(@Nullable final String value) {
293+
if (value == null) return null;
294+
try {
295+
return Integer.parseInt(value);
296+
} catch (final NumberFormatException ex) {
297+
return null;
298+
}
299+
}
300+
301+
@Nullable private static Float parseFloatValue(@Nullable final String value) {
302+
if (value == null) return null;
303+
try {
304+
return Float.parseFloat(value);
305+
} catch (final NumberFormatException ex) {
306+
return null;
307+
}
308+
}
309+
310+
@Nullable private static Long parseLongValue(@Nullable final String value) {
311+
if (value == null) return null;
312+
try {
313+
return Long.parseLong(value);
314+
} catch (final NumberFormatException ex) {
315+
return null;
316+
}
317+
}
318+
319+
@Nullable private static UUID parseUUIDValue(@Nullable final String value) {
320+
if (value == null) return null;
321+
return UUID.fromString(value);
322+
}
323+
324+
@Nullable private static OffsetDateTime parseOffsetDateTimeValue(@Nullable final String value) {
325+
if (value == null) return null;
326+
try {
327+
return OffsetDateTime.parse(value);
328+
} catch (DateTimeParseException ex) {
329+
try {
330+
LocalDateTime localDateTime = LocalDateTime.parse(value);
331+
return localDateTime.atOffset(ZoneOffset.UTC);
332+
} catch (DateTimeParseException ex2) {
333+
throw ex;
334+
}
335+
}
336+
}
337+
338+
@Nullable private static LocalDate parseLocalDateValue(@Nullable final String value) {
339+
if (value == null) return null;
340+
return LocalDate.parse(value);
341+
}
342+
343+
@Nullable private static LocalTime parseLocalTimeValue(@Nullable final String value) {
344+
if (value == null) return null;
345+
return LocalTime.parse(value);
346+
}
347+
348+
@Nullable private static PeriodAndDuration parsePeriodAndDurationValue(
349+
@Nullable final String value) {
350+
if (value == null) return null;
351+
return PeriodAndDuration.parse(value);
352+
}
353+
275354
@Nullable public <T extends Parsable> List<T> getCollectionOfObjectValues(
276355
@Nonnull final ParsableFactory<T> factory) {
277356
throw new RuntimeException(

components/serialization/json/src/main/java/com/microsoft/kiota/serialization/JsonParseNode.java

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,10 @@ public JsonParseNode(@Nonnull final JsonElement node, @Nonnull final Gson gson)
127127
return gson.fromJson(currentNode, PeriodAndDuration.class);
128128
}
129129

130-
@Nullable private <T> T getPrimitiveValue(
131-
@Nonnull final Class<T> targetClass, @Nonnull final JsonParseNode itemNode) {
132-
return gson.fromJson(itemNode.currentNode, targetClass);
133-
}
134-
135130
private <T> List<T> iterateOnArray(JsonElement jsonElement, Function<JsonParseNode, T> fn) {
136131
JsonArray array = jsonElement.getAsJsonArray();
137132
final Iterator<JsonElement> sourceIterator = array.iterator();
138-
final List<T> result = new ArrayList<>();
133+
final List<T> result = new ArrayList<>(array.size());
139134
while (sourceIterator.hasNext()) {
140135
final JsonElement item = sourceIterator.next();
141136
final JsonParseNode itemNode = createNewNode(item);
@@ -151,8 +146,12 @@ private <T> List<T> iterateOnArray(JsonElement jsonElement, Function<JsonParseNo
151146
if (currentNode.isJsonNull()) {
152147
return null;
153148
} else if (currentNode.isJsonArray()) {
154-
return iterateOnArray(
155-
currentNode, itemNode -> getPrimitiveValue(targetClass, itemNode));
149+
final JsonArray array = currentNode.getAsJsonArray();
150+
final List<T> result = new ArrayList<>(array.size());
151+
for (final JsonElement item : array) {
152+
result.add(gson.fromJson(item, targetClass));
153+
}
154+
return result;
156155
} else throw new RuntimeException("invalid state expected to have an array node");
157156
}
158157

@@ -172,7 +171,20 @@ private <T> List<T> iterateOnArray(JsonElement jsonElement, Function<JsonParseNo
172171
if (currentNode.isJsonNull()) {
173172
return null;
174173
} else if (currentNode.isJsonArray()) {
175-
return iterateOnArray(currentNode, itemNode -> itemNode.getEnumValue(enumParser));
174+
final JsonArray array = currentNode.getAsJsonArray();
175+
final List<T> result = new ArrayList<>(array.size());
176+
for (final JsonElement item : array) {
177+
if (!item.isJsonNull()) {
178+
final String rawValue = gson.fromJson(item, String.class);
179+
if (rawValue != null && !rawValue.isEmpty()) {
180+
final T value = enumParser.forValue(rawValue);
181+
if (value != null) {
182+
result.add(value);
183+
}
184+
}
185+
}
186+
}
187+
return result;
176188
} else throw new RuntimeException("invalid state expected to have an array node");
177189
}
178190

@@ -202,17 +214,17 @@ else if (element.isJsonPrimitive()) {
202214
HashMap<String, UntypedNode> propertiesMap = new HashMap<>();
203215
for (final Map.Entry<String, JsonElement> fieldEntry :
204216
element.getAsJsonObject().entrySet()) {
205-
final String fieldKey = fieldEntry.getKey();
206-
final JsonElement fieldValue = fieldEntry.getValue();
207-
final JsonParseNode childNode = createNewNode(fieldValue);
208-
childNode.setOnBeforeAssignFieldValues(this.getOnBeforeAssignFieldValues());
209-
childNode.setOnAfterAssignFieldValues(this.getOnAfterAssignFieldValues());
210-
propertiesMap.put(fieldKey, childNode.getUntypedValue());
217+
propertiesMap.put(fieldEntry.getKey(), getUntypedValue(fieldEntry.getValue()));
211218
}
212219
return new UntypedObject(propertiesMap);
213220

214221
} else if (element.isJsonArray()) {
215-
return new UntypedArray(iterateOnArray(element, JsonParseNode::getUntypedValue));
222+
final JsonArray array = element.getAsJsonArray();
223+
final List<UntypedNode> result = new ArrayList<>(array.size());
224+
for (final JsonElement item : array) {
225+
result.add(getUntypedValue(item));
226+
}
227+
return new UntypedArray(result);
216228
}
217229

218230
throw new RuntimeException(

0 commit comments

Comments
 (0)