diff --git a/jr-annotation-support/pom.xml b/jr-annotation-support/pom.xml index a0289570..46d71ecf 100644 --- a/jr-annotation-support/pom.xml +++ b/jr-annotation-support/pom.xml @@ -76,6 +76,14 @@ org.gradlex gradle-module-metadata-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + diff --git a/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java b/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java index 4d6ad64b..c74ca090 100644 --- a/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java +++ b/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.jr.ob.impl.JSONReader; import com.fasterxml.jackson.jr.ob.impl.JSONWriter; import com.fasterxml.jackson.jr.ob.impl.POJODefinition; +import com.fasterxml.jackson.jr.ob.impl.RecordsHelpers; /** * @@ -91,18 +92,30 @@ protected POJODefinition introspectDefinition() constructors = null; } else { constructors = new BeanConstructors(_type); - for (Constructor ctor : _type.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(_type)) { + Constructor canonical = RecordsHelpers.findCanonicalConstructor(_type); + constructors.addRecordConstructor(canonical); + for (int i = 0; i < canonical.getParameterCount(); i++) { + Parameter ctorParam = canonical.getParameters()[i]; + final String explName = _findExplicitName(ctorParam); + if (explName != null) { + constructors.addRecordConstructorAlias(explName, ctorParam.getType(), i); + } + } + } else { + for (Constructor ctor : _type.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); + } } } } diff --git a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicAliasTest.java b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicAliasTest.java index 1f4d0230..7d8fdf38 100644 --- a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicAliasTest.java +++ b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicAliasTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.jr.ob.JSON; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,6 +35,11 @@ public String getMiddle() { public void setLast(String str) { last = str; } } + record SnakeCaseRecord( + @JsonProperty("first_name") String firstName, + @JsonProperty("last_name") String lastName + ) {} + /* /********************************************************************** /* Test methods @@ -62,4 +68,20 @@ public void testSimpleAliases() throws Exception assertEquals("Bob", result.middle); assertEquals("Burger", result.last); } + + public void testSnakeCaseRecordDeserialization() throws Exception + { + final String input = a2q("{ 'first_name':'John', 'last_name':'Doe' }"); + SnakeCaseRecord result; + + // First: without setting, nothing matches + result = JSON.std.beanFrom(SnakeCaseRecord.class, input); + assertNull(result.firstName()); + assertNull(result.lastName()); + + // but with annotations it's all good... + result = JSON_WITH_ANNO.beanFrom(SnakeCaseRecord.class, input); + assertEquals("John", result.firstName()); + assertEquals("Doe", result.lastName()); + } } diff --git a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicIgnoralTest.java b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicIgnoralTest.java index ac660b1a..86f27361 100644 --- a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicIgnoralTest.java +++ b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicIgnoralTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.jr.ob.JSON; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -47,6 +48,11 @@ public XYZ(int x, int y, int z) { } } + record SnakeCaseRecord( + @JsonIgnore int x, + @JsonProperty("y_value") int y + ) {} + private final JSON JSON_WITH_ANNO = jsonWithAnnotationSupport(); private final JSON JSON_WITH_ANNO_WITH_STATIC = JSON.builder().register(JacksonAnnotationExtension.std).enable(JSON.Feature.INCLUDE_STATIC_FIELDS).build(); @@ -118,6 +124,23 @@ public void testPropertyIgnoreWithUnknown() throws Exception assertEquals(new XY().x, result.x); } +// Probably another bug that needs to be fixed. First I'm focusing on the deserialization of records. +// +// public void testSnakeCaseRecordDeserialization() throws Exception +// { +// final String input = a2q("{ 'x':1, 'y_value':2 }"); +// SnakeCaseRecord result; +// +// // First: without setting, nothing matches +// result = JSON.std.beanFrom(SnakeCaseRecord.class, input); +// assertNull(result); +// +// // but with annotations it's all good... +// result = JSON_WITH_ANNO.beanFrom(SnakeCaseRecord.class, input); +// assertEquals(0, result.x()); +// assertEquals(2, result.y()); +// } + /* /********************************************************************** /* Tests for @JsonIgnoreProperties diff --git a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicRenameTest.java b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicRenameTest.java index 971b7d9e..94ad1e7e 100644 --- a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicRenameTest.java +++ b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicRenameTest.java @@ -116,7 +116,10 @@ public static SubclassingEnum from(String id) { } } - + record SnakeCaseRecord( + @JsonProperty("first_name") String firstName, + @JsonProperty("last_name") String lastName + ) {} /* /********************************************************************** diff --git a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicReorderTest.java b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicReorderTest.java index d35f9e77..414fb73b 100644 --- a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicReorderTest.java +++ b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicReorderTest.java @@ -1,7 +1,7 @@ package com.fasterxml.jackson.jr.annotationsupport; import com.fasterxml.jackson.annotation.JsonPropertyOrder; - +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.jr.ob.JSON; import org.junit.jupiter.api.Test; @@ -40,6 +40,11 @@ public FullNameBean(String f, String m, String l) { public void setLast(String n) { last = n; } } + record SnakeCaseRecord( + @JsonProperty("first_name") String firstName, + @JsonProperty("last_name") String lastName + ) {} + private final JSON JSON_WITH_ANNO = jsonWithAnnotationSupport(); @Test @@ -73,4 +78,20 @@ public void testPartialReorder() throws Exception // and ensure no leakage to default one: assertEquals(EXP_DEFAULT, JSON.std.asString(input)); } + + public void testSnakeCaseRecordDeserialization() throws Exception + { + final String input = a2q("{ 'first_name':'John', 'last_name':'Doe' }"); + SnakeCaseRecord result; + + // First: without setting, nothing matches + result = JSON.std.beanFrom(SnakeCaseRecord.class, input); + assertNull(result.firstName()); + assertNull(result.lastName()); + + // but with annotations it's all good... + result = JSON_WITH_ANNO.beanFrom(SnakeCaseRecord.class, input); + assertEquals("John", result.firstName()); + assertEquals("Doe", result.lastName()); + } } diff --git a/jr-objects/pom.xml b/jr-objects/pom.xml index 906b44db..5c3c06d7 100644 --- a/jr-objects/pom.xml +++ b/jr-objects/pom.xml @@ -67,6 +67,14 @@ has no other dependencies, and provides additional builder-style content generat org.gradlex gradle-module-metadata-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + 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 88700d59..995d5e80 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 @@ -1,6 +1,10 @@ package com.fasterxml.jackson.jr.ob.impl; +import com.fasterxml.jackson.jr.ob.api.ValueReader; + import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Map; /** * Container class added to encapsulate details of collection and use of @@ -10,6 +14,9 @@ */ public class BeanConstructors { + public record RecordAlias(ValueReader reader, Integer pos) { + + } protected final Class _valueType; protected Constructor _noArgsCtor; @@ -21,10 +28,17 @@ public class BeanConstructors */ protected Constructor _recordCtor; + /** + * Aliases for Record constructor parameters. + * + * @since 2.20 + */ + protected Map _recordCtorAliases = new HashMap<>(); + protected Constructor _intCtor; + protected Constructor _longCtor; protected Constructor _stringCtor; - public BeanConstructors(Class valueType) { _valueType = valueType; } @@ -42,6 +56,14 @@ public BeanConstructors addRecordConstructor(Constructor ctor) { return this; } + /** + * @since 2.20 + */ + public BeanConstructors addRecordConstructorAlias(String alias, Class valueType, int pos) { + _recordCtorAliases.put(alias, new RecordAlias(new SimpleValueReader(valueType, 9), pos)); // TODO: Should not always be a simplevaluereader + return this; + } + public BeanConstructors addIntConstructor(Constructor ctor) { _intCtor = ctor; return this; @@ -57,6 +79,14 @@ public BeanConstructors addStringConstructor(Constructor ctor) { return this; } + public RecordAlias getRecordCtorAliasValueReader(String name) { + return _recordCtorAliases.get(name); + } + + public int getRecordCtorAliasesCount() { + return _recordCtorAliases.size(); + } + public void forceAccess() { if (_noArgsCtor != null) { _noArgsCtor.setAccessible(true); 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 5c57cfcf..0575fa05 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 @@ -137,10 +137,17 @@ public Object readNext(JSONReader r, JsonParser p) throws IOException } private Object readRecord(JSONReader r, JsonParser p) throws Exception { - final Object[] values = new Object[_propsByName.size()]; + final Object[] values = new Object[_propsByName.size() + _constructors.getRecordCtorAliasesCount()]; String propName; for (; (propName = p.nextFieldName()) != null;) { + BeanConstructors.RecordAlias recAlias; + if ((recAlias = _constructors.getRecordCtorAliasValueReader(propName)) != null) { + values[recAlias.pos()] = recAlias.reader().readNext(r, p); + + continue; + } + BeanPropertyReader prop = findProperty(propName); if (prop == null) { handleUnknown(r, p, propName); 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 0b2893c2..c005f094 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 @@ -37,7 +37,7 @@ public final class RecordsHelpers { } private RecordsHelpers() {} - static Constructor findCanonicalConstructor(Class beanClass) { + public static Constructor findCanonicalConstructor(Class beanClass) { // sanity check: caller shouldn't rely on it if (!supportsRecords || !isRecordType(beanClass)) { return null; @@ -78,7 +78,7 @@ static boolean isRecordConstructor(Class beanClass, Constructor ctor, } } - static boolean isRecordType(Class cls) { + public static boolean isRecordType(Class cls) { Class parent = cls.getSuperclass(); return (parent != null) && "java.lang.Record".equals(parent.getName()); }