Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for Java 17 & Groovy Records #130

Merged
merged 13 commits into from
Mar 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ public enum Feature
*/
USE_IS_GETTERS(true, true),

/**
* Feature that provides support for Groovy & JDK14 records, by allowing
Shounaks marked this conversation as resolved.
Show resolved Hide resolved
* reading of "non-get-getters" in a class, (like for a field named <code>amount</code>
* the getter would be <code>amount()</code>)
* */
Shounaks marked this conversation as resolved.
Show resolved Hide resolved
USE_FIELD_NAME_GETTERS(false,true),
Shounaks marked this conversation as resolved.
Show resolved Hide resolved

/**
* Feature that enables use of public fields instead of setters and getters,
* in cases where no setter/getter is available.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;

import com.fasterxml.jackson.jr.ob.JSON;
import com.fasterxml.jackson.jr.ob.impl.POJODefinition.Prop;
import com.fasterxml.jackson.jr.ob.impl.POJODefinition.PropBuilder;


import static com.fasterxml.jackson.jr.ob.JSON.Feature.INCLUDE_STATIC_FIELDS;
import static com.fasterxml.jackson.jr.ob.JSON.Feature.USE_FIELD_NAME_GETTERS;

/**
* Helper class that jackson-jr uses by default to introspect POJO properties
* (represented as {@link POJODefinition}) to build general POJO readers
Expand Down Expand Up @@ -97,7 +101,9 @@ private static void _introspect(Class<?> currType, Map<String, PropBuilder> prop
// First, check base type
_introspect(currType.getSuperclass(), props, features);

final boolean noStatics = JSON.Feature.INCLUDE_STATIC_FIELDS.isDisabled(features);
final boolean noStatics = INCLUDE_STATIC_FIELDS.isDisabled(features);
final boolean isFieldNameGettersEnabled = USE_FIELD_NAME_GETTERS.isEnabled(features);

// then public fields (since 2.8); may or may not be ultimately included
// but at this point still possible
for (Field f : currType.getDeclaredFields()) {
Expand Down Expand Up @@ -146,6 +152,17 @@ private static void _introspect(Class<?> currType, Map<String, PropBuilder> prop
_propFrom(props, name).withIsGetter(m);
}
}
else if (isFieldNameGettersEnabled) {
// This will allow getters with field name as their getters, like the ones generated by Groovy
// If method name matches with field name, & method return type matches with field type
// only then it can be considered a direct name getter.
final String decapName = name;
Shounaks marked this conversation as resolved.
Show resolved Hide resolved
Arrays.stream(currType.getDeclaredFields())
.filter(f -> f.getName().equals(m.getName()))
.filter(f -> Modifier.isPublic(m.getModifiers()) && m.getReturnType().equals(f.getType()))
.findFirst()
.ifPresent(f -> _propFrom(props, decap(decapName)).withGetter(m));
}
} else if (argTypes.length == 1) { // setter?
// Non-public setters are fine if we can force access, don't yet check
// let's also not bother about return type; setters that return value are fine
Expand Down
5 changes: 5 additions & 0 deletions jr-test-module/src/test/groovy/GroovyObjectSupportTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import com.fasterxml.jackson.jr.ob.JSON
import org.junit.Assert
import org.junit.Test

/**
* A minor note on running/debugging this test on local, if you are using intellij, please
* change `<packaging>pom</packaging>` to `<packaging>bundle</packaging>`. this is causing
* some issue with the IDE.
*/
class GroovyObjectSupportTest {
@Test
void testSimpleGroovyObject() throws Exception {
Expand Down
67 changes: 67 additions & 0 deletions jr-test-module/src/test/groovy/GroovyRecordsTest.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import com.fasterxml.jackson.jr.ob.JSON
import org.junit.Assert
import org.junit.Test

/**
* A minor note on running/debugging this test on local, if you are using intellij, please
* change `<packaging>pom</packaging>` to `<packaging>bundle</packaging>`. this is causing
* some issue with the IDE.
*/
class GroovyRecordsTest {

@Test
void testRecord() throws Exception {
/* We need to use this since build (8, ubuntu-20.04), will fail Map.of() was added in Java 9*/
def map = new HashMap<String, String>()
map.put("foo", "bar")

def json = JSON.builder().enable(JSON.Feature.USE_FIELD_NAME_GETTERS).build().asString(new Cow("foo", map))
def expected = """{"message":"foo","object":{"foo":"bar"}}"""
Assert.assertEquals(expected, json)
}

@Test
void testRecordEquivalentObjects() throws Exception {
def expected = """{"message":"foo","object":{"foo":"bar"}}"""

/* We need to use this since build (8, ubuntu-20.04), will fail Map.of() was added in Java 9*/
def map = new HashMap<String, String>()
map.put("foo", "bar")

def json = JSON.builder().enable(JSON.Feature.USE_FIELD_NAME_GETTERS).build().asString(new SimpleGroovyObject("foo", map))
Assert.assertEquals(expected, json)

def json2 = JSON.builder().enable(JSON.Feature.USE_FIELD_NAME_GETTERS).build().asString(new GroovyObjectWithNamedGetters("foo", map))
Assert.assertEquals(expected, json2)
}
}

class SimpleGroovyObject {
public final String message
public final Map<String, String> object

SimpleGroovyObject(String message, Map<String, String> object) {
this.message = message
this.object = object
}
}

class GroovyObjectWithNamedGetters {
private final String message
private final Map<String, String> object

GroovyObjectWithNamedGetters(String message, Map<String, String> object) {
this.message = message
this.object = object
}

String message() {
return message
}

Map<String, String> object() {
return object
}
}

record Cow(String message, Map<String, String> object) {}
22 changes: 22 additions & 0 deletions jr-test-module/src/test/java/Java14RecordTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import com.fasterxml.jackson.jr.ob.JSON;
import org.junit.Assert;
import org.junit.Test;

import java.io.IOException;
import java.util.Map;

/**
* This test is in test module since the JDK version to be tested is higher than other, and hence supports Records.
*/
public class Java14RecordTest {
Shounaks marked this conversation as resolved.
Show resolved Hide resolved

@Test
public void testJava14RecordSupport() throws IOException {
var expectedString = "{\"message\":\"MOO\",\"object\":{\"Foo\":\"Bar\"}}";
var json = JSON.builder().enable(JSON.Feature.USE_FIELD_NAME_GETTERS).build().asString(new Cow("MOO", Map.of("Foo", "Bar")));
Assert.assertEquals(expectedString, json);
}

record Cow(String message, Map<String, String> object) {
}
}