Record optional parameters (#543) Signed-off-by: David Kral <david.k.kral@oracle.com>
diff --git a/src/main/java/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java b/src/main/java/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java index c7adaa1..1f805cf 100644 --- a/src/main/java/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java +++ b/src/main/java/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java
@@ -36,6 +36,16 @@ return true; } + /** + * Whether the class is Record or not. + * + * @param clazz class to check + * @return is record + */ + public static boolean isRecord(Class<?> clazz) { + return false; + } + static boolean isSpecialAccessorMethod(Method method, Map<String, Property> classProperties) { return false; }
diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/ObjectDeserializer.java b/src/main/java/org/eclipse/yasson/internal/serializer/ObjectDeserializer.java index 38c7ddf..07fcc25 100644 --- a/src/main/java/org/eclipse/yasson/internal/serializer/ObjectDeserializer.java +++ b/src/main/java/org/eclipse/yasson/internal/serializer/ObjectDeserializer.java
@@ -15,9 +15,16 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.function.Supplier; import javax.json.bind.JsonbException; import javax.json.bind.serializer.JsonbDeserializer; @@ -42,6 +49,28 @@ */ class ObjectDeserializer<T> extends AbstractContainerDeserializer<T> { + private static final Map<Class<?>, Supplier<Object>> DEFAULT_CREATOR_VALUES; + private static final Supplier<Object> NULL_PROVIDER = () -> null; + + static { + Map<Class<?>, Supplier<Object>> tmpValuesMap = new HashMap<>(); + + tmpValuesMap.put(byte.class, () -> (byte) 0); + tmpValuesMap.put(short.class, () -> (short) 0); + tmpValuesMap.put(int.class, () -> 0); + tmpValuesMap.put(long.class, () -> 0L); + tmpValuesMap.put(float.class, () -> 0.0F); + tmpValuesMap.put(double.class, () -> 0.0); + tmpValuesMap.put(char.class, () -> '\u0000'); + tmpValuesMap.put(boolean.class, () -> false); + tmpValuesMap.put(Optional.class, Optional::empty); + tmpValuesMap.put(OptionalInt.class, OptionalInt::empty); + tmpValuesMap.put(OptionalLong.class, OptionalLong::empty); + tmpValuesMap.put(OptionalDouble.class, OptionalDouble::empty); + + DEFAULT_CREATOR_VALUES = Collections.unmodifiableMap(tmpValuesMap); + } + /** * Last property model cache to avoid lookup by jsonKey on every access. */ @@ -123,13 +152,18 @@ private T createInstance(Class<T> rawType, JsonbCreator creator) { final T instance; final List<Object> paramValues = new ArrayList<>(); + boolean isRecord = ClassMultiReleaseExtension.isRecord(rawType); for (CreatorModel param : creator.getParams()) { final ValueWrapper valueWrapper = values.get(param.getName()); //required by spec - if (valueWrapper == null) { + if (!isRecord && valueWrapper == null) { throw new JsonbException(Messages.getMessage(MessageKeys.JSONB_CREATOR_MISSING_PROPERTY, param.getName())); + } else if (valueWrapper == null) { + Class<?> rawParamType = ReflectionUtils.getRawType(param.getType()); + paramValues.add(DEFAULT_CREATOR_VALUES.getOrDefault(rawParamType, NULL_PROVIDER).get()); + } else { + paramValues.add(valueWrapper.getValue()); } - paramValues.add(valueWrapper.getValue()); } instance = creator.call(paramValues.toArray(), rawType); return instance;
diff --git a/src/main/java16/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java b/src/main/java16/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java index d62e7fb..442ab59 100644 --- a/src/main/java16/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java +++ b/src/main/java16/org/eclipse/yasson/internal/ClassMultiReleaseExtension.java
@@ -35,7 +35,11 @@ } static boolean shouldTransformToPropertyName(Method method) { - return !method.getDeclaringClass().isRecord(); + return !isRecord(method.getDeclaringClass()); + } + + public static boolean isRecord(Class<?> clazz) { + return clazz.isRecord(); } static boolean isSpecialAccessorMethod(Method method, Map<String, Property> classProperties) {
diff --git a/src/test/java16/org/eclipse/yasson/records/CarCreatorOptionalTest.java b/src/test/java16/org/eclipse/yasson/records/CarCreatorOptionalTest.java new file mode 100644 index 0000000..540a777 --- /dev/null +++ b/src/test/java16/org/eclipse/yasson/records/CarCreatorOptionalTest.java
@@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package org.eclipse.yasson.records; + +import java.util.Optional; + +import javax.json.bind.annotation.JsonbCreator; +import javax.json.bind.annotation.JsonbProperty; + +public record CarCreatorOptionalTest(String type, int somePrimitive, String color) { + + @JsonbCreator + public CarCreatorOptionalTest(@JsonbProperty("type") Optional<String> type, + @JsonbProperty("somePrimitive") int somePrimitive, + @JsonbProperty("color") String color) { + this(type.orElse("typeDefaultValue"), somePrimitive, color); + } +}
diff --git a/src/test/java16/org/eclipse/yasson/records/RecordTest.java b/src/test/java16/org/eclipse/yasson/records/RecordTest.java index a7c3e97..73d733e 100644 --- a/src/test/java16/org/eclipse/yasson/records/RecordTest.java +++ b/src/test/java16/org/eclipse/yasson/records/RecordTest.java
@@ -94,4 +94,19 @@ assertEquals(car, deserialized); } + @Test + public void testRecordParameterOptionality() { + CarCreatorOptionalTest car = new CarCreatorOptionalTest("skoda", 123, "red"); + String expected = "{\"color\":\"red\",\"somePrimitive\":123,\"type\":\"skoda\"}"; + + String json = Jsonbs.defaultJsonb.toJson(car); + assertEquals(expected, json); + + String toDeserialize = "{}"; + String expectedDefaultValues = "{\"color\":null,\"somePrimitive\":0,\"type\":\"typeDefaultValue\"}"; + CarCreatorOptionalTest deserialized = Jsonbs.defaultJsonb.fromJson(toDeserialize, CarCreatorOptionalTest.class); + json = Jsonbs.nullableJsonb.toJson(deserialized); + assertEquals(expectedDefaultValues, json); + } + }