From e2ef799edb78a34c3fc6b67cfd5c89b453b05db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Tue, 11 Jun 2024 17:20:34 +0200 Subject: [PATCH 01/20] Add java record deserialization --- .../com/fasterxml/jackson/jr/ob/JSON.java | 6 ++-- .../jackson/jr/ob/impl/BeanConstructors.java | 16 +++++++++++ .../jr/ob/impl/BeanPropertyIntrospector.java | 18 ++++++------ .../jackson/jr/ob/impl/BeanReader.java | 17 +++++++++++ .../jackson/jr/ob/impl/RecordsHelpers.java | 10 ++++--- .../jr/ob/impl/ValueReaderLocator.java | 28 +++++++++++++------ .../test-jdk17/java/jr/Java17RecordTest.java | 23 +++++++++++++-- 7 files changed, 90 insertions(+), 28 deletions(-) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/JSON.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/JSON.java index c02d2903..e6a4e542 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/JSON.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/JSON.java @@ -262,11 +262,11 @@ public enum Feature USE_IS_GETTERS(true, true), /** - * Feature that provides serialization support for Groovy and Java 17 records, by allowing + * Feature that provides serialization support for Groovy & Java 17 records, by allowing * reading of "non-get-getters" in a class, (like for a field named amount * the getter would be amount()). - *

- * Feature is disabled by default for backward compatibility. + * + * @implNote

Feature is disabled by default for backward compatibility.

* * @since 2.17 */ diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanConstructors.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanConstructors.java index 0cf07735..00cc7bde 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanConstructors.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanConstructors.java @@ -13,6 +13,7 @@ public class BeanConstructors protected final Class _valueType; protected Constructor _noArgsCtor; + protected Constructor _recordCtor; protected Constructor _intCtor; protected Constructor _longCtor; @@ -27,6 +28,11 @@ public BeanConstructors addNoArgsConstructor(Constructor ctor) { return this; } + public BeanConstructors addRecordConstructor(Constructor ctor) { + _recordCtor = ctor; + return this; + } + public BeanConstructors addIntConstructor(Constructor ctor) { _intCtor = ctor; return this; @@ -46,6 +52,9 @@ public void forceAccess() { if (_noArgsCtor != null) { _noArgsCtor.setAccessible(true); } + if (_recordCtor != null) { + _recordCtor.setAccessible(true); + } if (_intCtor != null) { _intCtor.setAccessible(true); } @@ -64,6 +73,13 @@ protected Object create() throws Exception { return _noArgsCtor.newInstance((Object[]) null); } + protected Object create(Object[] components) throws Exception { + if (_recordCtor == null) { + throw new IllegalStateException("Class "+_valueType.getName()+" does not have record constructor to use"); + } + return _recordCtor.newInstance(components); + } + protected Object create(String str) throws Exception { if (_stringCtor == null) { throw new IllegalStateException("Class "+_valueType.getName()+" does not have single-String constructor to use"); diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java index 34e439a4..f1ede1f9 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java @@ -68,6 +68,8 @@ private POJODefinition _introspectDefinition(Class beanType, } else if (argType == Long.class || argType == Long.TYPE) { constructors.addLongConstructor(ctor); } + } else if (RecordsHelpers.isRecordConstructor(beanType, ctor, propsByName)) { + constructors.addRecordConstructor(ctor); } } } @@ -96,11 +98,7 @@ private static void _introspect(Class currType, Map prop _introspect(currType.getSuperclass(), props, features); final boolean noStatics = JSON.Feature.INCLUDE_STATIC_FIELDS.isDisabled(features); - - // 14-Jun-2024, tatu: Need to enable "matching getters" naming style for Java Records - // too, regardless of `Feature.USE_FIELD_MATCHING_GETTERS` - final boolean isFieldNameGettersEnabled = JSON.Feature.USE_FIELD_MATCHING_GETTERS.isEnabled(features) - || RecordsHelpers.isRecordType(currType); + final boolean isFieldNameGettersEnabled = JSON.Feature.USE_FIELD_MATCHING_GETTERS.isEnabled(features); final Map fieldNameMap = isFieldNameGettersEnabled ? new HashMap<>() : null; @@ -152,15 +150,14 @@ private static void _introspect(Class currType, Map prop name = decap(name.substring(2)); _propFrom(props, name).withIsGetter(m); } - } else if (isFieldNameGettersEnabled){ + } else if (isFieldNameGettersEnabled) { // 10-Mar-2024: [jackson-jr#94]: // This will allow getters with field name as their getters, // like the ones generated by Groovy (or JDK 17 for Records). // If method name matches with field name, & method return // type matches the field type only then it can be considered a getter. Field field = fieldNameMap.get(name); - if (field != null && Modifier.isPublic(m.getModifiers()) - && m.getReturnType().equals(field.getType())) { + if (field != null && Modifier.isPublic(m.getModifiers()) && m.getReturnType().equals(field.getType())) { // NOTE: do NOT decap, field name should be used as-is _propFrom(props, name).withGetter(m); } @@ -200,8 +197,9 @@ private static String decap(String name) { /** * Helper method to detect Groovy's problematic metadata accessor type. - * Groovy MetaClass have cyclic reference, and hence the class containing it should not be - * serialized without either removing that reference, or skipping over such references. + * + * @implNote Groovy MetaClass have cyclic reference, and hence the class containing it should not be serialised without + * either removing that reference, or skipping over such references. */ protected static boolean isGroovyMetaClass(Class clazz) { return "groovy.lang.MetaClass".equals(clazz.getName()); diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java index c07974b3..2fd96697 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java @@ -36,6 +36,8 @@ public class BeanReader */ protected final BeanConstructors _constructors; + protected boolean _isRecordType; + /** * Constructors used for deserialization use case * @@ -56,6 +58,7 @@ public BeanReader(Class type, Map props, aliasMapping = Collections.emptyMap(); } _aliasMapping = aliasMapping; + _isRecordType = RecordsHelpers.isRecordType(type); } @Deprecated // since 2.17 @@ -155,6 +158,20 @@ public Object read(JSONReader r, JsonParser p) throws IOException return _constructors.create(p.getLongValue()); case START_OBJECT: { + if (_isRecordType) { + final List values = new ArrayList<>(); + + String propName; + for (; (propName = p.nextFieldName()) != null;) { + BeanPropertyReader prop = findProperty(propName); + if (prop == null) { + handleUnknown(r, p, propName); + continue; + } + values.add(prop.getReader().readNext(r, p)); + } + return _constructors.create(values.toArray()); + } Object bean = _constructors.create(); String propName; final Object[] valueBuf = r._setterBuffer; diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java index 939fd277..83abf8f1 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java @@ -8,32 +8,34 @@ import java.util.Map; /** - * Helper class to get Java Record metadata, from Java 8 (not using - * JDK 17 methods) - * - * @since 2.18 + * Helper class to get Java Record metadata. */ public final class RecordsHelpers { private static boolean supportsRecords; + private static Method isRecordMethod; private static Method getRecordComponentsMethod; private static Method getTypeMethod; static { + Method isRecordMethod; Method getRecordComponentsMethod; Method getTypeMethod; try { + isRecordMethod = Class.class.getMethod("isRecord"); getRecordComponentsMethod = Class.class.getMethod("getRecordComponents"); Class recordComponentClass = Class.forName("java.lang.reflect.RecordComponent"); getTypeMethod = recordComponentClass.getMethod("getType"); supportsRecords = true; } catch (Throwable t) { + isRecordMethod = null; getRecordComponentsMethod = null; getTypeMethod = null; supportsRecords = false; } + RecordsHelpers.isRecordMethod = isRecordMethod; RecordsHelpers.getRecordComponentsMethod = getRecordComponentsMethod; RecordsHelpers.getTypeMethod = getTypeMethod; } diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java index 3a2b94e5..e589eddc 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java @@ -452,6 +452,7 @@ protected BeanReader _resolveBeanForDeser(Class raw, POJODefinition beanDef) final Map propMap; Map aliasMapping = null; + boolean isRecord = RecordsHelpers.isRecordType(raw); if (len == 0) { propMap = Collections.emptyMap(); } else { @@ -473,16 +474,25 @@ protected BeanReader _resolveBeanForDeser(Class raw, POJODefinition beanDef) setter = null; } } - // if no setter, field would do as well - if (setter == null) { - if (field == null) { - continue; + if (isRecord) { + try { + field = raw.getDeclaredField(rawProp.name); + } catch (NoSuchFieldException e) { + throw new IllegalStateException("Cannot access field " + rawProp.name + + " of record class " + raw.getName(), e); } - // fields should always be public, but let's just double-check - if (forceAccess) { - field.setAccessible(true); - } else if (!Modifier.isPublic(field.getModifiers())) { - continue; + } else { + // if no setter, field would do as well + if (setter == null) { + if (field == null) { + continue; + } + // fields should always be public, but let's just double-check + if (forceAccess) { + field.setAccessible(true); + } else if (!Modifier.isPublic(field.getModifiers())) { + continue; + } } } diff --git a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java index 2a5d14de..c59b0072 100644 --- a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java +++ b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java @@ -1,10 +1,13 @@ package jr; +import java.io.IOException; import java.util.Map; import com.fasterxml.jackson.jr.ob.JSON; +import com.fasterxml.jackson.jr.ob.JSON.Feature; import junit.framework.TestCase; +import org.junit.Assert; /** * This test is in test module since the JDK version to be tested is higher than other, and hence supports Records. @@ -16,7 +19,23 @@ public record Cow(String message, Map object) { // [jackson-jr#94]: Record serialization public void testJava14RecordSerialization() throws Exception { - assertEquals("{\"message\":\"MOO\",\"object\":{\"Foo\":\"Bar\"}}", - JSON.std.asString(new Cow("MOO", Map.of("Foo", "Bar")))); + JSON jsonParser = JSON.builder().enable(Feature.USE_FIELD_MATCHING_GETTERS).build(); + var expectedString = "{\"message\":\"MOO\",\"object\":{\"Foo\":\"Bar\"}}"; + Cow expectedObject = new Cow("MOO", Map.of("Foo", "Bar")); + + var json = jsonParser.asString(expectedObject); + Assert.assertEquals(expectedString, json); + + Cow object = jsonParser.beanFrom(Cow.class, json); + Assert.assertEquals(expectedObject, object); + } + + public void testDifferentOrder() throws IOException { + JSON jsonParser = JSON.builder().enable(Feature.USE_FIELD_MATCHING_GETTERS).build(); + var json = "{\"object\":{\"Foo\":\"Bar\"}, \"message\":\"MOO\"}"; + + Cow expectedObject = new Cow("MOO", Map.of("Foo", "Bar")); + Cow object = jsonParser.beanFrom(Cow.class, json); + Assert.assertEquals(expectedObject, object); } } From e9292712ef4ccd989eba00826be533affe62d5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 18:40:18 +0200 Subject: [PATCH 02/20] Handling parameters by index --- jr-objects/pom.xml | 5 ++ .../jr/ob/impl/BeanPropertyReader.java | 10 ++- .../jackson/jr/ob/impl/BeanReader.java | 73 ++++++++++++------- .../jr/ob/impl/ValueReaderLocator.java | 4 +- .../test-jdk17/java/jr/Java17RecordTest.java | 64 ++++++++++++++-- .../src/test-jdk17/java/jr/Wrapper.java | 50 +++++++++++++ 6 files changed, 167 insertions(+), 39 deletions(-) create mode 100644 jr-record-test/src/test-jdk17/java/jr/Wrapper.java diff --git a/jr-objects/pom.xml b/jr-objects/pom.xml index 19d3a065..5ba0f3d9 100644 --- a/jr-objects/pom.xml +++ b/jr-objects/pom.xml @@ -40,6 +40,11 @@ has no other dependencies, and provides additional builder-style content generat jackson-core ${jackson.version.core} + + it.unimi.dsi + fastutil-core + 8.5.13 + diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java index 016e3cbe..32a3f0c9 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java @@ -31,7 +31,9 @@ public final class BeanPropertyReader */ private final Field _field; - public BeanPropertyReader(String name, Field f, Method setter) { + private final int _index; + + public BeanPropertyReader(String name, Field f, Method setter, int propertyIndex) { if ((f == null) && (setter == null)) { throw new IllegalArgumentException("Both `field` and `setter` can not be null"); } @@ -39,12 +41,14 @@ public BeanPropertyReader(String name, Field f, Method setter) { _field = f; _setter = setter; _valueReader = null; + _index = propertyIndex; } protected BeanPropertyReader(BeanPropertyReader src, ValueReader vr) { _name = src._name; _field = src._field; _setter = src._setter; + _index = src._index; _valueReader = vr; } @@ -69,6 +73,10 @@ public Class rawSetterType() { public ValueReader getReader() { return _valueReader; } public String getName() { return _name; } + public int getIndex() { + return _index; + } + public void setValueFor(Object bean, Object[] valueBuf) throws IOException { diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java index 2fd96697..9000c1c9 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.jr.ob.JSON; import com.fasterxml.jackson.jr.ob.JSONObjectException; import com.fasterxml.jackson.jr.ob.api.ValueReader; +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; /** * Class that contains information about dynamically introspected @@ -38,6 +40,8 @@ public class BeanReader protected boolean _isRecordType; + protected Object2IntMap propertyPositions = new Object2IntArrayMap<>(); + /** * Constructors used for deserialization use case * @@ -59,6 +63,10 @@ public BeanReader(Class type, Map props, } _aliasMapping = aliasMapping; _isRecordType = RecordsHelpers.isRecordType(type); + + props.values().forEach(prop -> { + propertyPositions.put(prop.getName(), prop.getIndex()); + }); } @Deprecated // since 2.17 @@ -107,24 +115,28 @@ public Object readNext(JSONReader r, JsonParser p) throws IOException return _constructors.create(p.getLongValue()); case START_OBJECT: { - Object bean = _constructors.create(); - final Object[] valueBuf = r._setterBuffer; - String propName; + if (_isRecordType) { + return readRecord(r, p); + } else { + Object bean = _constructors.create(); + final Object[] valueBuf = r._setterBuffer; + String propName; - for (; (propName = p.nextFieldName()) != null; ) { - BeanPropertyReader prop = findProperty(propName); - if (prop == null) { - handleUnknown(r, p, propName); - continue; + for (; (propName = p.nextFieldName()) != null; ) { + BeanPropertyReader prop = findProperty(propName); + if (prop == null) { + handleUnknown(r, p, propName); + continue; + } + valueBuf[0] = prop.getReader().readNext(r, p); + prop.setValueFor(bean, valueBuf); } - valueBuf[0] = prop.getReader().readNext(r, p); - prop.setValueFor(bean, valueBuf); + // also verify we are not confused... + if (!p.hasToken(JsonToken.END_OBJECT)) { + throw _reportProblem(p); + } + return bean; } - // also verify we are not confused... - if (!p.hasToken(JsonToken.END_OBJECT)) { - throw _reportProblem(p); - } - return bean; } default: } @@ -138,7 +150,23 @@ public Object readNext(JSONReader r, JsonParser p) throws IOException throw JSONObjectException.from(p, "Can not create a "+_valueType.getName()+" instance out of "+_tokenDesc(p)); } - + + private Object readRecord(JSONReader r, JsonParser p) throws Exception { + final Object[] values = new Object[propertiesByName().size()]; + + String propName; + for (; (propName = p.nextFieldName()) != null;) { + BeanPropertyReader prop = findProperty(propName); + if (prop == null) { + handleUnknown(r, p, propName); + continue; + } + Object value = prop.getReader().readNext(r, p); + values[prop.getIndex()] = value; + } + return _constructors.create(values); + } + /** * Method used for deserialization; will read an instance of the bean * type using given parser. @@ -159,18 +187,7 @@ public Object read(JSONReader r, JsonParser p) throws IOException case START_OBJECT: { if (_isRecordType) { - final List values = new ArrayList<>(); - - String propName; - for (; (propName = p.nextFieldName()) != null;) { - BeanPropertyReader prop = findProperty(propName); - if (prop == null) { - handleUnknown(r, p, propName); - continue; - } - values.add(prop.getReader().readNext(r, p)); - } - return _constructors.create(values.toArray()); + return readRecord(r, p); } Object bean = _constructors.create(); String propName; diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java index e589eddc..5daa77b1 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java @@ -496,9 +496,9 @@ protected BeanReader _resolveBeanForDeser(Class raw, POJODefinition beanDef) } } - propMap.put(rawProp.name, new BeanPropertyReader(rawProp.name, field, setter)); + propMap.put(rawProp.name, new BeanPropertyReader(rawProp.name, field, setter, i)); - // 25-Jan-2020, tatu: Aliases are bit different because we can not tie them into + // 25-Jan-2020, tatu: Aliases are a bit different because we can not tie them into // specific reader instance, due to resolution of cyclic dependencies. Instead, // we must link via name of primary property, unfortunately: if (rawProp.hasAliases()) { diff --git a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java index c59b0072..29ad3019 100644 --- a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java +++ b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java @@ -7,35 +7,83 @@ import com.fasterxml.jackson.jr.ob.JSON.Feature; import junit.framework.TestCase; -import org.junit.Assert; /** * This test is in test module since the JDK version to be tested is higher than other, and hence supports Records. */ public class Java17RecordTest extends TestCase { + + private final JSON jsonParser = JSON.builder().enable(Feature.USE_FIELD_MATCHING_GETTERS).build(); + public record Cow(String message, Map object) { } // [jackson-jr#94]: Record serialization public void testJava14RecordSerialization() throws Exception { - JSON jsonParser = JSON.builder().enable(Feature.USE_FIELD_MATCHING_GETTERS).build(); - var expectedString = "{\"message\":\"MOO\",\"object\":{\"Foo\":\"Bar\"}}"; + var expectedString = """ + {"message":"MOO","object":{"Foo":"Bar"}}"""; Cow expectedObject = new Cow("MOO", Map.of("Foo", "Bar")); var json = jsonParser.asString(expectedObject); - Assert.assertEquals(expectedString, json); + assertEquals(expectedString, json); Cow object = jsonParser.beanFrom(Cow.class, json); - Assert.assertEquals(expectedObject, object); + assertEquals(expectedObject, object); } public void testDifferentOrder() throws IOException { - JSON jsonParser = JSON.builder().enable(Feature.USE_FIELD_MATCHING_GETTERS).build(); - var json = "{\"object\":{\"Foo\":\"Bar\"}, \"message\":\"MOO\"}"; + var json = """ + {"object":{"Foo":"Bar"}, "message":"MOO"}"""; Cow expectedObject = new Cow("MOO", Map.of("Foo", "Bar")); Cow object = jsonParser.beanFrom(Cow.class, json); - Assert.assertEquals(expectedObject, object); + assertEquals(expectedObject, object); + } + + public void testNullAndRecord() throws IOException { + var json = """ + {"object": null, "message":"MOO"}"""; + + Cow expectedObject = new Cow("MOO", null); + Cow object = jsonParser.beanFrom(Cow.class, json); + assertEquals(expectedObject, object); + + assertEquals(new Cow(null, null), jsonParser.beanFrom(Cow.class,"{}")); + assertNull(jsonParser.beanFrom(Cow.class, "null")); + } + + public void testWhenInsideObject() throws IOException { + var cowJson = """ + {"object": null, "message":"MOO"}"""; + var json = """ + { "cow": %s, "farmerName": "Bob" }""".formatted(cowJson); + + Wrapper wrapper = new Wrapper(); + wrapper.setCow(new Cow("MOO", null)); + wrapper.setFarmerName("Bob"); + + Wrapper object = jsonParser.beanFrom(Wrapper.class, json); + assertEquals(wrapper, object); + + var jsonNullCow = """ + { "cow": null, "farmerName": "Bob" }"""; + + wrapper = new Wrapper(); + wrapper.setCow(null); + wrapper.setFarmerName("Bob"); + + object = jsonParser.beanFrom(Wrapper.class, jsonNullCow); + assertEquals(wrapper, object); + + var jsonNoCow = """ + { "farmerName": "Bob" }"""; + + wrapper = new Wrapper(); + wrapper.setCow(null); + wrapper.setFarmerName("Bob"); + + object = jsonParser.beanFrom(Wrapper.class, jsonNoCow); + assertEquals(wrapper, object); } } diff --git a/jr-record-test/src/test-jdk17/java/jr/Wrapper.java b/jr-record-test/src/test-jdk17/java/jr/Wrapper.java new file mode 100644 index 00000000..725575f9 --- /dev/null +++ b/jr-record-test/src/test-jdk17/java/jr/Wrapper.java @@ -0,0 +1,50 @@ +package jr; + +import jr.Java17RecordTest.Cow; + +import java.util.Objects; + +public final class Wrapper { + Cow cow; + String farmerName; + + public Cow getCow() { + return cow; + } + + public void setCow(Cow cow) { + this.cow = cow; + } + + public String getFarmerName() { + return farmerName; + } + + public void setFarmerName(String farmerName) { + this.farmerName = farmerName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Wrapper wrapper)) { + return false; + } + return Objects.equals(cow, wrapper.cow) && Objects.equals(farmerName, wrapper.farmerName); + } + + @Override + public int hashCode() { + return Objects.hash(cow, farmerName); + } + + @Override + public String toString() { + return "Wrapper{" + + "cow=" + cow + + ", farmerName='" + farmerName + '\'' + + '}'; + } +} From 65071f57a62fd9e528382cbba886345b177a5c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 18:42:59 +0200 Subject: [PATCH 03/20] Partial parsing test --- .../src/test-jdk17/java/jr/Java17RecordTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java index 29ad3019..4881b489 100644 --- a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java +++ b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java @@ -53,6 +53,15 @@ public void testNullAndRecord() throws IOException { assertNull(jsonParser.beanFrom(Cow.class, "null")); } + public void testPartialParsing() throws IOException { + var json = """ + { "message":"MOO"}"""; + + Cow expectedObject = new Cow("MOO", null); + Cow object = jsonParser.beanFrom(Cow.class, json); + assertEquals(expectedObject, object); + } + public void testWhenInsideObject() throws IOException { var cowJson = """ {"object": null, "message":"MOO"}"""; From 17f8e7dc22ebf6edd530b8e18b0c7976be532a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 18:47:09 +0200 Subject: [PATCH 04/20] Nested test --- .../src/test-jdk17/java/jr/Java17RecordTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java index 4881b489..6dd2fd68 100644 --- a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java +++ b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java @@ -19,6 +19,9 @@ public class Java17RecordTest extends TestCase public record Cow(String message, Map object) { } + public record WrapperRecord(Cow cow, String hello) { + } + // [jackson-jr#94]: Record serialization public void testJava14RecordSerialization() throws Exception { var expectedString = """ @@ -95,4 +98,17 @@ public void testWhenInsideObject() throws IOException { object = jsonParser.beanFrom(Wrapper.class, jsonNoCow); assertEquals(wrapper, object); } + + public void testNested() throws IOException { + var json = """ + { + "cow": { "message":"MOO"}, + "hello": "world" + } + """; + + var expected = new WrapperRecord(new Cow("MOO", null), "world"); + var object = jsonParser.beanFrom(WrapperRecord.class, json); + assertEquals(expected, object); + } } From a21e87c92c9475768ba19fc03239ead1b3306c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 18:50:59 +0200 Subject: [PATCH 05/20] Revert accidental changes --- .../src/main/java/com/fasterxml/jackson/jr/ob/JSON.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/JSON.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/JSON.java index e6a4e542..c02d2903 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/JSON.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/JSON.java @@ -262,11 +262,11 @@ public enum Feature USE_IS_GETTERS(true, true), /** - * Feature that provides serialization support for Groovy & Java 17 records, by allowing + * Feature that provides serialization support for Groovy and Java 17 records, by allowing * reading of "non-get-getters" in a class, (like for a field named amount * the getter would be amount()). - * - * @implNote

Feature is disabled by default for backward compatibility.

+ *

+ * Feature is disabled by default for backward compatibility. * * @since 2.17 */ From 4e5851ca28a0f8c1f2d1171436ca0aa920ff0c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 19:58:04 +0200 Subject: [PATCH 06/20] Remove dep --- jr-objects/pom.xml | 5 ----- .../java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java | 4 +--- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/jr-objects/pom.xml b/jr-objects/pom.xml index 5ba0f3d9..19d3a065 100644 --- a/jr-objects/pom.xml +++ b/jr-objects/pom.xml @@ -40,11 +40,6 @@ has no other dependencies, and provides additional builder-style content generat jackson-core ${jackson.version.core} - - it.unimi.dsi - fastutil-core - 8.5.13 - diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java index 9000c1c9..1110d762 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java @@ -9,8 +9,6 @@ import com.fasterxml.jackson.jr.ob.JSON; import com.fasterxml.jackson.jr.ob.JSONObjectException; import com.fasterxml.jackson.jr.ob.api.ValueReader; -import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; /** * Class that contains information about dynamically introspected @@ -40,7 +38,7 @@ public class BeanReader protected boolean _isRecordType; - protected Object2IntMap propertyPositions = new Object2IntArrayMap<>(); + protected Map propertyPositions = new HashMap<>(); /** * Constructors used for deserialization use case From bfb26cccbe342d37912475a4442b741cc2305859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 19:59:50 +0200 Subject: [PATCH 07/20] Javadoc --- .../com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java index 32a3f0c9..94c20180 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java @@ -31,6 +31,9 @@ public final class BeanPropertyReader */ private final Field _field; + /** + * Index used for {@code Record}s constructor parameters. It is not used for getter/setter methods. + */ private final int _index; public BeanPropertyReader(String name, Field f, Method setter, int propertyIndex) { From a4db4d25272e44ac043c2622240c5afa2ff35a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 20:00:16 +0200 Subject: [PATCH 08/20] Added since --- .../java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java index 83abf8f1..ef2b5e7a 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java @@ -9,6 +9,8 @@ /** * Helper class to get Java Record metadata. + * + * @since 2.18 */ public final class RecordsHelpers { private static boolean supportsRecords; From f7b7e9cafe7b49ea30d3805bd14731f9ae8dcfb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 20:01:00 +0200 Subject: [PATCH 09/20] Remove unused method --- .../com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java index ef2b5e7a..b0ffcdaa 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java @@ -15,29 +15,24 @@ public final class RecordsHelpers { private static boolean supportsRecords; - private static Method isRecordMethod; private static Method getRecordComponentsMethod; private static Method getTypeMethod; static { - Method isRecordMethod; Method getRecordComponentsMethod; Method getTypeMethod; try { - isRecordMethod = Class.class.getMethod("isRecord"); getRecordComponentsMethod = Class.class.getMethod("getRecordComponents"); Class recordComponentClass = Class.forName("java.lang.reflect.RecordComponent"); getTypeMethod = recordComponentClass.getMethod("getType"); supportsRecords = true; } catch (Throwable t) { - isRecordMethod = null; getRecordComponentsMethod = null; getTypeMethod = null; supportsRecords = false; } - RecordsHelpers.isRecordMethod = isRecordMethod; RecordsHelpers.getRecordComponentsMethod = getRecordComponentsMethod; RecordsHelpers.getTypeMethod = getTypeMethod; } From 6ab1d63cffb36c6d88888054ff72e08d33b7cac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 20:14:41 +0200 Subject: [PATCH 10/20] nesting object inside test --- .../test-jdk17/java/jr/Java17RecordTest.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java index 6dd2fd68..504394e4 100644 --- a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java +++ b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java @@ -22,6 +22,9 @@ public record Cow(String message, Map object) { public record WrapperRecord(Cow cow, String hello) { } + public record RecordWithWrapper(Cow cow, Wrapper nested) { + } + // [jackson-jr#94]: Record serialization public void testJava14RecordSerialization() throws Exception { var expectedString = """ @@ -102,8 +105,8 @@ public void testWhenInsideObject() throws IOException { public void testNested() throws IOException { var json = """ { - "cow": { "message":"MOO"}, - "hello": "world" + "hello": "world", + "cow": { "message":"MOO"} } """; @@ -111,4 +114,23 @@ public void testNested() throws IOException { var object = jsonParser.beanFrom(WrapperRecord.class, json); assertEquals(expected, object); } + + public void testNestedObjects() throws IOException { + var json = """ + { + "nested": { + "farmerName": "Bob", + "cow": { "message":"MOOO"} + }, + "cow": { "message":"MOO"} + } + """; + + Wrapper nested = new Wrapper(); + nested.setCow(new Cow("MOOO", null)); + nested.setFarmerName("Bob"); + var expected = new RecordWithWrapper(new Cow("MOO", null), nested); + var object = jsonParser.beanFrom(RecordWithWrapper.class, json); + assertEquals(expected, object); + } } From 88b6c3d8bac998be288c62540444322371a837e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Gaw=C4=99da?= Date: Thu, 27 Jun 2024 20:15:22 +0200 Subject: [PATCH 11/20] nesting object inside test --- jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java index 504394e4..f707640e 100644 --- a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java +++ b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java @@ -22,7 +22,7 @@ public record Cow(String message, Map object) { public record WrapperRecord(Cow cow, String hello) { } - public record RecordWithWrapper(Cow cow, Wrapper nested) { + public record RecordWithWrapper(Cow cow, Wrapper nested, int someInt) { } // [jackson-jr#94]: Record serialization @@ -122,6 +122,7 @@ public void testNestedObjects() throws IOException { "farmerName": "Bob", "cow": { "message":"MOOO"} }, + "someInt": 1337, "cow": { "message":"MOO"} } """; @@ -129,7 +130,7 @@ public void testNestedObjects() throws IOException { Wrapper nested = new Wrapper(); nested.setCow(new Cow("MOOO", null)); nested.setFarmerName("Bob"); - var expected = new RecordWithWrapper(new Cow("MOO", null), nested); + var expected = new RecordWithWrapper(new Cow("MOO", null), nested, 1337); var object = jsonParser.beanFrom(RecordWithWrapper.class, json); assertEquals(expected, object); } From c6da0b38f374b5ccc34f279e77754bd271f600f6 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 30 Jun 2024 11:12:47 -0700 Subject: [PATCH 12/20] Update release notes too --- .../jackson/jr/ob/impl/BeanConstructors.java | 14 +++++++++++++- .../fasterxml/jackson/jr/ob/impl/BeanReader.java | 2 +- release-notes/CREDITS-2.x | 5 +++++ release-notes/VERSION-2.x | 3 ++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanConstructors.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanConstructors.java index 00cc7bde..88700d59 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanConstructors.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanConstructors.java @@ -13,6 +13,12 @@ public class BeanConstructors protected final Class _valueType; protected Constructor _noArgsCtor; + + /** + * Constructor (canonical) used when deserializing Java Record types. + * + * @since 2.18 + */ protected Constructor _recordCtor; protected Constructor _intCtor; @@ -28,6 +34,9 @@ public BeanConstructors addNoArgsConstructor(Constructor ctor) { return this; } + /** + * @since 2.18 + */ public BeanConstructors addRecordConstructor(Constructor ctor) { _recordCtor = ctor; return this; @@ -73,7 +82,10 @@ protected Object create() throws Exception { return _noArgsCtor.newInstance((Object[]) null); } - protected Object create(Object[] components) throws Exception { + /** + * @since 2.18 + */ + protected Object createRecord(Object[] components) throws Exception { if (_recordCtor == null) { throw new IllegalStateException("Class "+_valueType.getName()+" does not have record constructor to use"); } diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java index 1110d762..5b97d152 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java @@ -162,7 +162,7 @@ private Object readRecord(JSONReader r, JsonParser p) throws Exception { Object value = prop.getReader().readNext(r, p); values[prop.getIndex()] = value; } - return _constructors.create(values); + return _constructors.createRecord(values); } /** diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 42d9bbc1..219773cf 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -64,3 +64,8 @@ Julian Honnen (@jhonnen) * Contributed fix for #90: `USE_BIG_DECIMAL_FOR_FLOATS` feature not working when using `JSON.treeFrom()` (2.17.1) + +Tomasz Gawęda (@TomaszGaweda) + +* Contributed #162: Add support for deserializing Java Records + (2.18.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index a2354c67..1685ce40 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -13,7 +13,8 @@ Modules: 2.18.0 (not yet released) -- +#162: Add support for deserializing Java Records + (contributed by Tomasz G) 2.17.1 (04-May-2024) From 8d363186d0e8eefdb3591aa25305ad5db7041f5c Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 30 Jun 2024 11:23:24 -0700 Subject: [PATCH 13/20] Minor cleanup --- .../jr/ob/impl/BeanPropertyIntrospector.java | 16 +++++---- .../jr/ob/impl/BeanPropertyReader.java | 8 +++++ .../jackson/jr/ob/impl/BeanReader.java | 35 +++++++++---------- .../jackson/jr/ob/impl/RecordsHelpers.java | 3 +- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java index f1ede1f9..96130b91 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java @@ -98,8 +98,10 @@ private static void _introspect(Class currType, Map prop _introspect(currType.getSuperclass(), props, features); final boolean noStatics = JSON.Feature.INCLUDE_STATIC_FIELDS.isDisabled(features); - final boolean isFieldNameGettersEnabled = JSON.Feature.USE_FIELD_MATCHING_GETTERS.isEnabled(features); - + // 14-Jun-2024, tatu: Need to enable "matching getters" naming style for Java Records + // too, regardless of `Feature.USE_FIELD_MATCHING_GETTERS` + final boolean isFieldNameGettersEnabled = JSON.Feature.USE_FIELD_MATCHING_GETTERS.isEnabled(features) + || RecordsHelpers.isRecordType(currType); final Map fieldNameMap = isFieldNameGettersEnabled ? new HashMap<>() : null; // then public fields (since 2.8); may or may not be ultimately included @@ -157,7 +159,8 @@ private static void _introspect(Class currType, Map prop // If method name matches with field name, & method return // type matches the field type only then it can be considered a getter. Field field = fieldNameMap.get(name); - if (field != null && Modifier.isPublic(m.getModifiers()) && m.getReturnType().equals(field.getType())) { + if (field != null && Modifier.isPublic(m.getModifiers()) + && m.getReturnType().equals(field.getType())) { // NOTE: do NOT decap, field name should be used as-is _propFrom(props, name).withGetter(m); } @@ -197,9 +200,10 @@ private static String decap(String name) { /** * Helper method to detect Groovy's problematic metadata accessor type. - * - * @implNote Groovy MetaClass have cyclic reference, and hence the class containing it should not be serialised without - * either removing that reference, or skipping over such references. + *

+ * NOTE: Groovy MetaClass have cyclic reference, and hence the class containing + * it should not be serialized without either removing that reference, + * or skipping over such references. */ protected static boolean isGroovyMetaClass(Class clazz) { return "groovy.lang.MetaClass".equals(clazz.getName()); diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java index 94c20180..96502ad8 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java @@ -36,6 +36,9 @@ public final class BeanPropertyReader */ private final int _index; + /** + * @since 2.18 + */ public BeanPropertyReader(String name, Field f, Method setter, int propertyIndex) { if ((f == null) && (setter == null)) { throw new IllegalArgumentException("Both `field` and `setter` can not be null"); @@ -47,6 +50,11 @@ public BeanPropertyReader(String name, Field f, Method setter, int propertyIndex _index = propertyIndex; } + @Deprecated // @since 2.18 + public BeanPropertyReader(String name, Field f, Method setter) { + this(name, f, setter, -1); + } + protected BeanPropertyReader(BeanPropertyReader src, ValueReader vr) { _name = src._name; _field = src._field; diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java index 5b97d152..78838f44 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java @@ -115,26 +115,25 @@ public Object readNext(JSONReader r, JsonParser p) throws IOException { if (_isRecordType) { return readRecord(r, p); - } else { - Object bean = _constructors.create(); - final Object[] valueBuf = r._setterBuffer; - String propName; - - for (; (propName = p.nextFieldName()) != null; ) { - BeanPropertyReader prop = findProperty(propName); - if (prop == null) { - handleUnknown(r, p, propName); - continue; - } - valueBuf[0] = prop.getReader().readNext(r, p); - prop.setValueFor(bean, valueBuf); - } - // also verify we are not confused... - if (!p.hasToken(JsonToken.END_OBJECT)) { - throw _reportProblem(p); + } + Object bean = _constructors.create(); + final Object[] valueBuf = r._setterBuffer; + String propName; + + for (; (propName = p.nextFieldName()) != null; ) { + BeanPropertyReader prop = findProperty(propName); + if (prop == null) { + handleUnknown(r, p, propName); + continue; } - return bean; + valueBuf[0] = prop.getReader().readNext(r, p); + prop.setValueFor(bean, valueBuf); } + // also verify we are not confused... + if (!p.hasToken(JsonToken.END_OBJECT)) { + throw _reportProblem(p); + } + return bean; } default: } diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java index b0ffcdaa..1b49cbf4 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java @@ -38,7 +38,8 @@ public final class RecordsHelpers { } private RecordsHelpers() {} - static boolean isRecordConstructor(Class beanClass, Constructor ctor, Map propsByName) { + static boolean isRecordConstructor(Class beanClass, Constructor ctor, + Map propsByName) { if (!supportsRecords || !isRecordType(beanClass)) { return false; } From b057d39f826208e8f1926e7bec1a49ec432e770c Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 30 Jun 2024 11:27:20 -0700 Subject: [PATCH 14/20] More tweaking --- .../jackson/jr/ob/impl/BeanPropertyIntrospector.java | 2 ++ .../fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java | 3 +++ .../java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java | 6 ++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java index 96130b91..40702293 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java @@ -98,10 +98,12 @@ private static void _introspect(Class currType, Map prop _introspect(currType.getSuperclass(), props, features); final boolean noStatics = JSON.Feature.INCLUDE_STATIC_FIELDS.isDisabled(features); + // 14-Jun-2024, tatu: Need to enable "matching getters" naming style for Java Records // too, regardless of `Feature.USE_FIELD_MATCHING_GETTERS` final boolean isFieldNameGettersEnabled = JSON.Feature.USE_FIELD_MATCHING_GETTERS.isEnabled(features) || RecordsHelpers.isRecordType(currType); + final Map fieldNameMap = isFieldNameGettersEnabled ? new HashMap<>() : null; // then public fields (since 2.8); may or may not be ultimately included diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java index 96502ad8..16bcccef 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java @@ -84,6 +84,9 @@ public Class rawSetterType() { public ValueReader getReader() { return _valueReader; } public String getName() { return _name; } + /** + * @since 2.18 + */ public int getIndex() { return _index; } diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java index 78838f44..e6d06a75 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java @@ -36,9 +36,9 @@ public class BeanReader */ protected final BeanConstructors _constructors; - protected boolean _isRecordType; + protected final boolean _isRecordType; - protected Map propertyPositions = new HashMap<>(); + //protected final Map propertyPositions = new HashMap<>(); /** * Constructors used for deserialization use case @@ -62,9 +62,11 @@ public BeanReader(Class type, Map props, _aliasMapping = aliasMapping; _isRecordType = RecordsHelpers.isRecordType(type); + /* props.values().forEach(prop -> { propertyPositions.put(prop.getName(), prop.getIndex()); }); + */ } @Deprecated // since 2.17 From feca6c0edf75276f9d92ea36e0dac87dd296edd7 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 30 Jun 2024 11:28:40 -0700 Subject: [PATCH 15/20] ... --- .../com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java index 16bcccef..e153728d 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyReader.java @@ -33,6 +33,8 @@ public final class BeanPropertyReader /** * Index used for {@code Record}s constructor parameters. It is not used for getter/setter methods. + * + * @since 2.18 */ private final int _index; From 2788abc03d8de2afb9b2515f1cedc7628d3168e0 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 30 Jun 2024 11:30:48 -0700 Subject: [PATCH 16/20] Remove feature setting that should not be needed --- .../src/test-jdk17/java/jr/Java17RecordTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java index f707640e..4ad6ad87 100644 --- a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java +++ b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java @@ -3,18 +3,17 @@ import java.io.IOException; import java.util.Map; -import com.fasterxml.jackson.jr.ob.JSON; - -import com.fasterxml.jackson.jr.ob.JSON.Feature; import junit.framework.TestCase; +import com.fasterxml.jackson.jr.ob.JSON; + /** * This test is in test module since the JDK version to be tested is higher than other, and hence supports Records. */ public class Java17RecordTest extends TestCase { - private final JSON jsonParser = JSON.builder().enable(Feature.USE_FIELD_MATCHING_GETTERS).build(); + private final JSON jsonParser = JSON.builder().build(); public record Cow(String message, Map object) { } From 9771b47bc3cf6d4c2b5b740e9a32fd3c1d07c4d5 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 30 Jun 2024 16:45:01 -0700 Subject: [PATCH 17/20] Add test for, fix handling of, single-parameter Records --- .../jr/ob/impl/BeanPropertyIntrospector.java | 35 ++++++----- .../test-jdk17/java/jr/Java17RecordTest.java | 61 ++++++++++++------- 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java index 40702293..72da6354 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java @@ -55,21 +55,28 @@ private POJODefinition _introspectDefinition(Class beanType, constructors = null; } else { constructors = new BeanConstructors(beanType); - for (Constructor ctor : beanType.getDeclaredConstructors()) { - Class[] argTypes = ctor.getParameterTypes(); - if (argTypes.length == 0) { - constructors.addNoArgsConstructor(ctor); - } else if (argTypes.length == 1) { - Class argType = argTypes[0]; - if (argType == String.class) { - constructors.addStringConstructor(ctor); - } else if (argType == Integer.class || argType == Integer.TYPE) { - constructors.addIntConstructor(ctor); - } else if (argType == Long.class || argType == Long.TYPE) { - constructors.addLongConstructor(ctor); + if (RecordsHelpers.isRecordType(beanType)) { + for (Constructor ctor : beanType.getDeclaredConstructors()) { + if (RecordsHelpers.isRecordConstructor(beanType, ctor, propsByName)) { + constructors.addRecordConstructor(ctor); + break; + } + } + } else { + for (Constructor ctor : beanType.getDeclaredConstructors()) { + Class[] argTypes = ctor.getParameterTypes(); + if (argTypes.length == 0) { + constructors.addNoArgsConstructor(ctor); + } else if (argTypes.length == 1) { + Class argType = argTypes[0]; + if (argType == String.class) { + constructors.addStringConstructor(ctor); + } else if (argType == Integer.class || argType == Integer.TYPE) { + constructors.addIntConstructor(ctor); + } else if (argType == Long.class || argType == Long.TYPE) { + constructors.addLongConstructor(ctor); + } } - } else if (RecordsHelpers.isRecordConstructor(beanType, ctor, propsByName)) { - constructors.addRecordConstructor(ctor); } } } diff --git a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java index 4ad6ad87..4c539960 100644 --- a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java +++ b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java @@ -1,6 +1,5 @@ package jr; -import java.io.IOException; import java.util.Map; import junit.framework.TestCase; @@ -12,8 +11,7 @@ */ public class Java17RecordTest extends TestCase { - - private final JSON jsonParser = JSON.builder().build(); + private final JSON jsonHandler = JSON.std; public record Cow(String message, Map object) { } @@ -24,50 +22,54 @@ public record WrapperRecord(Cow cow, String hello) { public record RecordWithWrapper(Cow cow, Wrapper nested, int someInt) { } + record SingleIntRecord(int value) { } + record SingleLongRecord(long value) { } + record SingleStringRecord(String value) { } + // [jackson-jr#94]: Record serialization public void testJava14RecordSerialization() throws Exception { var expectedString = """ {"message":"MOO","object":{"Foo":"Bar"}}"""; Cow expectedObject = new Cow("MOO", Map.of("Foo", "Bar")); - var json = jsonParser.asString(expectedObject); + var json = jsonHandler.asString(expectedObject); assertEquals(expectedString, json); - Cow object = jsonParser.beanFrom(Cow.class, json); + Cow object = jsonHandler.beanFrom(Cow.class, json); assertEquals(expectedObject, object); } - public void testDifferentOrder() throws IOException { + public void testDifferentOrder() throws Exception { var json = """ {"object":{"Foo":"Bar"}, "message":"MOO"}"""; Cow expectedObject = new Cow("MOO", Map.of("Foo", "Bar")); - Cow object = jsonParser.beanFrom(Cow.class, json); + Cow object = jsonHandler.beanFrom(Cow.class, json); assertEquals(expectedObject, object); } - public void testNullAndRecord() throws IOException { + public void testNullAndRecord() throws Exception { var json = """ {"object": null, "message":"MOO"}"""; Cow expectedObject = new Cow("MOO", null); - Cow object = jsonParser.beanFrom(Cow.class, json); + Cow object = jsonHandler.beanFrom(Cow.class, json); assertEquals(expectedObject, object); - assertEquals(new Cow(null, null), jsonParser.beanFrom(Cow.class,"{}")); - assertNull(jsonParser.beanFrom(Cow.class, "null")); + assertEquals(new Cow(null, null), jsonHandler.beanFrom(Cow.class,"{}")); + assertNull(jsonHandler.beanFrom(Cow.class, "null")); } - public void testPartialParsing() throws IOException { + public void testPartialParsing() throws Exception { var json = """ { "message":"MOO"}"""; Cow expectedObject = new Cow("MOO", null); - Cow object = jsonParser.beanFrom(Cow.class, json); + Cow object = jsonHandler.beanFrom(Cow.class, json); assertEquals(expectedObject, object); } - public void testWhenInsideObject() throws IOException { + public void testWhenInsideObject() throws Exception { var cowJson = """ {"object": null, "message":"MOO"}"""; var json = """ @@ -77,7 +79,7 @@ public void testWhenInsideObject() throws IOException { wrapper.setCow(new Cow("MOO", null)); wrapper.setFarmerName("Bob"); - Wrapper object = jsonParser.beanFrom(Wrapper.class, json); + Wrapper object = jsonHandler.beanFrom(Wrapper.class, json); assertEquals(wrapper, object); var jsonNullCow = """ @@ -87,7 +89,7 @@ public void testWhenInsideObject() throws IOException { wrapper.setCow(null); wrapper.setFarmerName("Bob"); - object = jsonParser.beanFrom(Wrapper.class, jsonNullCow); + object = jsonHandler.beanFrom(Wrapper.class, jsonNullCow); assertEquals(wrapper, object); var jsonNoCow = """ @@ -97,11 +99,11 @@ public void testWhenInsideObject() throws IOException { wrapper.setCow(null); wrapper.setFarmerName("Bob"); - object = jsonParser.beanFrom(Wrapper.class, jsonNoCow); + object = jsonHandler.beanFrom(Wrapper.class, jsonNoCow); assertEquals(wrapper, object); } - public void testNested() throws IOException { + public void testNested() throws Exception { var json = """ { "hello": "world", @@ -110,11 +112,11 @@ public void testNested() throws IOException { """; var expected = new WrapperRecord(new Cow("MOO", null), "world"); - var object = jsonParser.beanFrom(WrapperRecord.class, json); + var object = jsonHandler.beanFrom(WrapperRecord.class, json); assertEquals(expected, object); } - public void testNestedObjects() throws IOException { + public void testNestedObjects() throws Exception { var json = """ { "nested": { @@ -130,7 +132,24 @@ public void testNestedObjects() throws IOException { nested.setCow(new Cow("MOOO", null)); nested.setFarmerName("Bob"); var expected = new RecordWithWrapper(new Cow("MOO", null), nested, 1337); - var object = jsonParser.beanFrom(RecordWithWrapper.class, json); + var object = jsonHandler.beanFrom(RecordWithWrapper.class, json); assertEquals(expected, object); } + + public void testSingleFieldRecords() throws Exception { + SingleIntRecord inputInt = new SingleIntRecord(42); + String json = jsonHandler.asString(inputInt); + assertEquals("{\"value\":42}", json); + assertEquals(inputInt, jsonHandler.beanFrom(SingleIntRecord.class, json)); + + SingleLongRecord inputLong = new SingleLongRecord(-1L); + json = jsonHandler.asString(inputLong); + assertEquals("{\"value\":-1}", json); + assertEquals(inputLong, jsonHandler.beanFrom(SingleLongRecord.class, json)); + + SingleStringRecord inputStr = new SingleStringRecord("abc"); + json = jsonHandler.asString(inputStr); + assertEquals("{\"value\":\"abc\"}", json); + assertEquals(inputStr, jsonHandler.beanFrom(SingleStringRecord.class, json)); + } } From addc975b01a78a5cb46a9d332b64a68d60d131c8 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 30 Jun 2024 16:48:04 -0700 Subject: [PATCH 18/20] Add test for no-fields ("empty") Record --- .../src/test-jdk17/java/jr/Java17RecordTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java index 4c539960..4ec0c176 100644 --- a/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java +++ b/jr-record-test/src/test-jdk17/java/jr/Java17RecordTest.java @@ -25,6 +25,9 @@ public record RecordWithWrapper(Cow cow, Wrapper nested, int someInt) { record SingleIntRecord(int value) { } record SingleLongRecord(long value) { } record SingleStringRecord(String value) { } + + // Degenerate case but supported: + record NoFieldsRecord() { } // [jackson-jr#94]: Record serialization public void testJava14RecordSerialization() throws Exception { @@ -136,6 +139,13 @@ public void testNestedObjects() throws Exception { assertEquals(expected, object); } + public void testNoFieldRecords() throws Exception { + String json = jsonHandler.asString(new NoFieldsRecord()); + assertEquals("{}", json); + assertEquals(new NoFieldsRecord(), + jsonHandler.beanFrom(NoFieldsRecord.class, json)); + } + public void testSingleFieldRecords() throws Exception { SingleIntRecord inputInt = new SingleIntRecord(42); String json = jsonHandler.asString(inputInt); From c05b61764e917a6132bfa7f82cefac7debf45b6e Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 30 Jun 2024 17:08:24 -0700 Subject: [PATCH 19/20] Further streamlining of Record constructor detection --- .../jr/ob/impl/BeanPropertyIntrospector.java | 10 +-- .../jackson/jr/ob/impl/RecordsHelpers.java | 77 ++++++++++++------- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java index 72da6354..889d008f 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java @@ -56,12 +56,12 @@ private POJODefinition _introspectDefinition(Class beanType, } else { constructors = new BeanConstructors(beanType); if (RecordsHelpers.isRecordType(beanType)) { - for (Constructor ctor : beanType.getDeclaredConstructors()) { - if (RecordsHelpers.isRecordConstructor(beanType, ctor, propsByName)) { - constructors.addRecordConstructor(ctor); - break; - } + Constructor canonical = RecordsHelpers.findCanonicalConstructor(beanType); + if (canonical == null) { // should never happen + throw new IllegalArgumentException( +"Unable to find canonical constructor of Record type `"+beanType.getClass().getName()+"`"); } + constructors.addRecordConstructor(canonical); } else { for (Constructor ctor : beanType.getDeclaredConstructors()) { Class[] argTypes = ctor.getParameterTypes(); diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java index 1b49cbf4..e18309fd 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/RecordsHelpers.java @@ -1,12 +1,12 @@ package com.fasterxml.jackson.jr.ob.impl; -import com.fasterxml.jackson.jr.ob.impl.POJODefinition.PropBuilder; - import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Map; +import com.fasterxml.jackson.jr.ob.impl.POJODefinition.PropBuilder; + /** * Helper class to get Java Record metadata. * @@ -16,30 +16,48 @@ public final class RecordsHelpers { private static boolean supportsRecords; private static Method getRecordComponentsMethod; - private static Method getTypeMethod; + private static Method getComponentTypeMethod; - static { - Method getRecordComponentsMethod; - Method getTypeMethod; + // We may need this in future: + //private static Method getComponentNameMethod; + static { try { getRecordComponentsMethod = Class.class.getMethod("getRecordComponents"); Class recordComponentClass = Class.forName("java.lang.reflect.RecordComponent"); - getTypeMethod = recordComponentClass.getMethod("getType"); + getComponentTypeMethod = recordComponentClass.getMethod("getType"); + //getComponentNameMethod = recordComponentClass.getMethod("getName"); supportsRecords = true; } catch (Throwable t) { - getRecordComponentsMethod = null; - getTypeMethod = null; supportsRecords = false; } - - RecordsHelpers.getRecordComponentsMethod = getRecordComponentsMethod; - RecordsHelpers.getTypeMethod = getTypeMethod; } private RecordsHelpers() {} + static Constructor findCanonicalConstructor(Class beanClass) { + // sanity check: caller shouldn't rely on it + if (!supportsRecords || !isRecordType(beanClass)) { + return null; + } + try { + final Class[] componentTypes = componentTypes(beanClass); + for (Constructor ctor : beanClass.getDeclaredConstructors()) { + final Class[] parameterTypes = ctor.getParameterTypes(); + if (parameterTypes.length == componentTypes.length) { + if (Arrays.equals(parameterTypes, componentTypes)) { + return ctor; + } + } + } + } catch (ReflectiveOperationException e) { + ; + } + return null; + } + static boolean isRecordConstructor(Class beanClass, Constructor ctor, - Map propsByName) { + Map propsByName) + { if (!supportsRecords || !isRecordType(beanClass)) { return false; } @@ -50,27 +68,28 @@ static boolean isRecordConstructor(Class beanClass, Constructor ctor, } try { - Object[] recordComponents = (Object[]) getRecordComponentsMethod.invoke(beanClass); - Class[] componentTypes = new Class[recordComponents.length]; - for (int i = 0; i < recordComponents.length; i++) { - Object recordComponent = recordComponents[i]; - Class type = (Class) getTypeMethod.invoke(recordComponent); - componentTypes[i] = type; - } - - for (int i = 0; i < parameterTypes.length; i++) { - if (parameterTypes[i] != componentTypes[i]) { - return false; - } - } - } catch (IllegalAccessException | InvocationTargetException e) { + Class[] componentTypes = componentTypes(beanClass); + return Arrays.equals(parameterTypes, componentTypes); + } catch (ReflectiveOperationException e) { return false; } - return true; } static boolean isRecordType(Class cls) { Class parent = cls.getSuperclass(); return (parent != null) && "java.lang.Record".equals(parent.getName()); } + + private static Class[] componentTypes(Class recordType) + throws ReflectiveOperationException + { + Object[] recordComponents = (Object[]) getRecordComponentsMethod.invoke(recordType); + Class[] componentTypes = new Class[recordComponents.length]; + for (int i = 0; i < recordComponents.length; i++) { + Object recordComponent = recordComponents[i]; + Class type = (Class) getComponentTypeMethod.invoke(recordComponent); + componentTypes[i] = type; + } + return componentTypes; + } } From 8811c04f1ff9b8f2f37f57b76f0f42077c766ee5 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Sun, 30 Jun 2024 17:10:09 -0700 Subject: [PATCH 20/20] Remove unused map --- .../fasterxml/jackson/jr/ob/impl/BeanReader.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java index e6d06a75..f93eae0f 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanReader.java @@ -38,8 +38,6 @@ public class BeanReader protected final boolean _isRecordType; - //protected final Map propertyPositions = new HashMap<>(); - /** * Constructors used for deserialization use case * @@ -61,12 +59,6 @@ public BeanReader(Class type, Map props, } _aliasMapping = aliasMapping; _isRecordType = RecordsHelpers.isRecordType(type); - - /* - props.values().forEach(prop -> { - propertyPositions.put(prop.getName(), prop.getIndex()); - }); - */ } @Deprecated // since 2.17 @@ -80,12 +72,6 @@ public BeanReader(Class type, Map props, ignorableNames, aliasMapping); } - @Deprecated // since 2.11 - public BeanReader(Class type, Map props, - Constructor defaultCtor, Constructor stringCtor, Constructor longCtor) { - this(type, props, defaultCtor, stringCtor, longCtor, null, null); - } - public Map propertiesByName() { return _propsByName; } public BeanPropertyReader findProperty(String name) {