From 05006acde3af5f5e163040ac6be8b2cb3ca7a12e Mon Sep 17 00:00:00 2001 From: Scott Lewis Date: Sat, 20 Jun 2026 13:40:45 -0700 Subject: [PATCH 1/8] Initial commit of jref implementation as a single ObjectMapper module...called JRefModule, in JRefModule.java. No changes to existing Jackson 3.0 classes, or other code additions are necessary. The tools.jackson.databind.JRefModule class has JRefValueSerializer and JRefValueSerializer implementations, used in appropriate circumstances during ser/des via the valueserializermodifier and the valuedeserializermodifier. Also included in this commit are initial versions (incomplete) test code for the JRefModule in new jref test package. Signed-off-by: Scott Lewis --- .../tools/jackson/databind/JRefModule.java | 338 ++++++++++++++++++ .../databind/jref/JRefAbstractTest.java | 38 ++ .../jackson/databind/jref/JRefArrayTest.java | 196 ++++++++++ ...efBeanNonPublicMemberDeserializerTest.java | 288 +++++++++++++++ .../jref/JRefBeanPublicMemberTest.java | 182 ++++++++++ .../JRefCircularAndNestedReferenceTests.java | 139 +++++++ .../jackson/databind/jref/JRefMapTest.java | 186 ++++++++++ .../databind/jref/JRefRecordBeanTest.java | 100 ++++++ .../jref/JRefSimpleNestedTypeTest.java | 76 ++++ 9 files changed, 1543 insertions(+) create mode 100644 src/main/java/tools/jackson/databind/JRefModule.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefArrayTest.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberTest.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefCircularAndNestedReferenceTests.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefMapTest.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefSimpleNestedTypeTest.java diff --git a/src/main/java/tools/jackson/databind/JRefModule.java b/src/main/java/tools/jackson/databind/JRefModule.java new file mode 100644 index 0000000000..4a578fda2b --- /dev/null +++ b/src/main/java/tools/jackson/databind/JRefModule.java @@ -0,0 +1,338 @@ +package tools.jackson.databind; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; + +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonPointer; +import tools.jackson.core.JsonToken; +import tools.jackson.databind.BeanDescription.Supplier; +import tools.jackson.databind.deser.ValueDeserializerModifier; +import tools.jackson.databind.deser.std.DelegatingDeserializer; +import tools.jackson.databind.jsontype.TypeDeserializer; +import tools.jackson.databind.jsontype.TypeSerializer; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.databind.node.TreeTraversingParser; +import tools.jackson.databind.ser.ValueSerializerModifier; +import tools.jackson.databind.ser.std.DelegatingSerializer; +import tools.jackson.databind.type.ArrayType; +import tools.jackson.databind.type.CollectionLikeType; +import tools.jackson.databind.type.CollectionType; +import tools.jackson.databind.type.MapLikeType; +import tools.jackson.databind.type.MapType; + +/** + * This Jackson 3 module extends Jackson's {@link ObjectMapper} serialization + * and de-serialization with a reference type (JRef). JRefs allow for circular + * references to be serialized as JSON or for multiple references to a target + * json object to be dealt with more efficiently than producing copies of that + * target object. This is especially useful for complex data structures such as + * trees and other object graphs where potentially many references to a single + * object may need to be serialized and de-serialized. + *

+ *

+ * References take the serialized form of { "$ref": "#/path/to/target" } where + * the URL fragment (starts with the '#' uri fragment identifier) is a JSON + * Pointer rfc-6901 + * locating a path local to the current document. + *

+ *

+ * The JRef + * specification can be used to reference locations in external documents as + * well (the JSON pointer specifies a full URI rather than just the fragment), + * but this implementation only supports references to objects in the current + * document. See the JRef specification and + * @hyperjump/browser for + * an implementation supporting such external references. + * + **/ +public class JRefModule extends SimpleModule { + + private static final long serialVersionUID = 1L; + public static final String JREF_NAME = "$ref"; + public static final String HASH = "#"; + + public JRefModule() { + super("JRefModule"); + } + + @Override + public void setupModule(SetupContext context) { + super.setupModule(context); + context.addDeserializerModifier(new JRefValueDeserializerModifier()); + context.addSerializerModifier(new JRefValueSerializerModifier()); + } + + public class JRefValueSerializerModifier extends ValueSerializerModifier { + + private static final long serialVersionUID = 1L; + + static final String PTR_MAP_ATTR = JRefValueSerializerModifier.class.getName() + ".ptrMap"; + + @FunctionalInterface + interface Serializer { + void serialize() throws RuntimeException; + } + + class JRefValueSerializer extends DelegatingSerializer { + + JRefValueSerializer(ValueSerializer delegatee) { + super(delegatee); + } + + void jrefSerialize(Object value, JsonGenerator gen, SerializationContext ctxt, Serializer serializer) { + @SuppressWarnings("unchecked") + Map valueToPtrMap = (Map) ctxt.getAttribute(PTR_MAP_ATTR); + // if it doesn't exist, then create and add as context attribute + if (valueToPtrMap == null) { + valueToPtrMap = new HashMap<>(); + ctxt.setAttribute(PTR_MAP_ATTR, valueToPtrMap); + } + JsonPointer ptr = valueToPtrMap.get(value); + if (ptr != null) { + // If JsonPointer found for value id, write it out and we're done! + gen.writeStartObject(); + gen.writeStringProperty(JREF_NAME, "#" + ptr.toString()); + gen.writeEndObject(); + } else { + // serialize the value with delegate + serializer.serialize(); + // put the object -> ptr into for possible reference usage + valueToPtrMap.put(value, JsonPointer.forPath(gen.streamWriteContext(), false)); + } + } + + @Override + public void serializeWithType(Object value, JsonGenerator gen, SerializationContext ctxt, + TypeSerializer typeSer) { + jrefSerialize(value, gen, ctxt, () -> super.serializeWithType(value, gen, ctxt, typeSer)); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctxt) { + jrefSerialize(value, gen, ctxt, () -> super.serialize(value, gen, ctxt)); + } + + @Override + public ValueSerializer newDelegatingInstance(ValueSerializer delegatee) { + return new JRefValueSerializer(delegatee); + } + + } + + @Override + public ValueSerializer modifySerializer(SerializationConfig config, Supplier beanDesc, + ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyArraySerializer(SerializationConfig config, ArrayType valueType, + Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyCollectionSerializer(SerializationConfig config, CollectionType valueType, + Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyCollectionLikeSerializer(SerializationConfig config, + CollectionLikeType valueType, Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyMapSerializer(SerializationConfig config, MapType valueType, Supplier beanDesc, + ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyMapLikeSerializer(SerializationConfig config, MapLikeType valueType, + Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyEnumSerializer(SerializationConfig config, JavaType valueType, + Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + } + + public class JRefValueDeserializerModifier extends ValueDeserializerModifier { + + private static final long serialVersionUID = 1L; + + static final String STACK_ATTR = JRefValueDeserializerModifier.class.getName() + ".callStack"; + static final String OBJECT_PTR_MAP_ATTR = JRefValueDeserializerModifier.class.getName() + ".objectPtrMap"; + + @FunctionalInterface + interface Deserializer { + Object deserialize(JsonParser p) throws RuntimeException; + } + + class JRefValueDeserializer extends DelegatingDeserializer { + + JRefValueDeserializer(ValueDeserializer src) { + super(src); + } + + Object jrefDeserialize(JsonParser p, DeserializationContext ctxt, Deserializer deserializer) { + @SuppressWarnings("unchecked") + Deque ptrStack = (Deque) ctxt.getAttribute(STACK_ATTR); + if (ptrStack == null) { + // Create on first access + ptrStack = new ArrayDeque<>(); + ctxt.setAttribute(STACK_ATTR, ptrStack); + } + JsonPointer parentPtr = ptrStack.peek(); + if (parentPtr == null) { + // use empty + parentPtr = JsonPointer.empty(); + } + JsonPointer ctxtPtr = JsonPointer.forPath(p.streamReadContext(), false); + // build currPtr from context and parent + JsonPointer currPtr = ctxtPtr.toString().startsWith(parentPtr.toString()) ? ctxtPtr + : parentPtr.append(ctxtPtr); + ptrStack.push(currPtr); + Object result = null; + if (p.currentToken() == JsonToken.START_OBJECT) { + JsonNode node = ctxt.readTree(p); + // Look for "$ref" property + JsonNode jrefValue = node.asObject().get(JREF_NAME); + if (jrefValue != null) { + String pathWithHashExpected = jrefValue.asString(); + // Must start with # (local-only json pointers) + if (!pathWithHashExpected.startsWith(HASH)) { + throw DatabindException.from(p, + String.format("JsonPointer value=%s must start with '#' character (local only)", + pathWithHashExpected)); + } + // Remove hash prefix (local only) + String path = pathWithHashExpected.substring(1); + try { + // create JsonPointer from jref path + JsonPointer pathPtr = JsonPointer.valueOf(path); + if (pathPtr.equals(JsonPointer.empty())) { + throw DatabindException.from(p, "JsonPointer cannot be empty"); + } + @SuppressWarnings("unchecked") + Map resultsMap = (Map) ctxt + .getAttribute(OBJECT_PTR_MAP_ATTR); + if (resultsMap != null) { + // lookup previous result with ptr + Object previousResult = resultsMap.get(pathPtr); + if (previousResult == null) { + throw DatabindException.from(p, + String.format("Could not find previous value for JsonPointer=%s", pathPtr)); + } + // result found + result = previousResult; + } else { + throw DatabindException.from(p, + String.format("No previous values present for JsonPointer=%", pathPtr)); + } + } catch (IllegalArgumentException e) { + throw DatabindException.from(p, String.format("Illegal JsonPointer=%s", path), e); + } + } + // If we have not found result via jref, then reset parser to + // TreeTraversingParser + if (result == null) { + p = new TreeTraversingParser(node); + if (p.currentToken() != JsonToken.END_OBJECT) { + p.nextToken(); + } + } + } + // call delegate deserializer if no result yet + if (result == null) { + // If jref result not found, delegate serialization by calling super class + result = deserializer.deserialize(p); + if (result != null) { + @SuppressWarnings("unchecked") + Map resultsMap = (Map) ctxt + .getAttribute(OBJECT_PTR_MAP_ATTR); + if (resultsMap == null) { + resultsMap = new HashMap<>(); + ctxt.setAttribute(OBJECT_PTR_MAP_ATTR, resultsMap); + } + resultsMap.put(currPtr, result); + } + } + ptrStack.pollFirst(); + return result; + } + + @Override + public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, + TypeDeserializer typeDeserializer) throws JacksonException { + return jrefDeserialize(p, ctxt, (p1) -> super.deserializeWithType(p1, ctxt, typeDeserializer)); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { + return jrefDeserialize(p, ctxt, (p1) -> super.deserialize(p1, ctxt)); + } + + @Override + protected ValueDeserializer newDelegatingInstance(ValueDeserializer newDelegatee) { + return new JRefValueDeserializer(newDelegatee); + } + + } + + @Override + public ValueDeserializer modifyArrayDeserializer(DeserializationConfig config, ArrayType valueType, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyEnumDeserializer(DeserializationConfig config, JavaType type, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyCollectionLikeDeserializer(DeserializationConfig config, + CollectionLikeType type, Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyDeserializer(DeserializationConfig config, Supplier beanDescRef, + ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyMapDeserializer(DeserializationConfig config, MapType type, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyMapLikeDeserializer(DeserializationConfig config, MapLikeType type, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + } + +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java b/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java new file mode 100644 index 0000000000..f8c529fa82 --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java @@ -0,0 +1,38 @@ +package tools.jackson.databind.jref; + +import static org.junit.Assert.assertEquals; +import static tools.jackson.databind.testutil.DatabindTestUtil.jsonMapperBuilder; + +import java.util.regex.Pattern; + +import tools.jackson.databind.JRefModule; +import tools.jackson.databind.ObjectMapper; + +public class JRefAbstractTest { + + protected ObjectMapper buildObjectMapperWithJRefSupport() { + return jsonMapperBuilder().addModule(new JRefModule()).build(); + } + + protected ObjectMapper buildObjectMapperWithoutJRefSupport() { + return jsonMapperBuilder().build(); + } + + static long countMatches(String text, String target) { + if (text == null || target == null || target.isEmpty()) return 0; + String quotedTarget = Pattern.quote(target); + + return Pattern.compile(quotedTarget) + .matcher(text) + .results() + .count(); + } + + protected void assertJRefCount(String input, long expectedJRefs) { + assertEquals(expectedJRefs, countMatches(input,"$ref")); + } + + void trace(String method, String s) { + System.out.println(method+"."+s); + } +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java b/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java new file mode 100644 index 0000000000..b9885dca47 --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java @@ -0,0 +1,196 @@ +package tools.jackson.databind.jref; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; + +public class JRefArrayTest extends JRefAbstractTest { + + @Test + void testObjectArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + Object o1 = new Object(); + Object o2 = o1; + Object[] arr = new Object[] { o1, o2 }; + String out = mapper.writeValueAsString(arr); + assertJRefCount(out, 1); + trace("testObjectArrayRef jrefserialized=",out); + Object[] oa = mapper.readValue(out, Object[].class); + assertTrue(oa[0] instanceof Map); + assertTrue(oa[1] instanceof Map); + assertEquals(oa[0],oa[1]); + } + + @Test + void testStringArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String o1 = new String("one"); + String o2 = o1; + String[] arr = new String[] { o1, o2 }; + String out = mapper.writeValueAsString(arr); + assertJRefCount(out, 1); + trace("testStringArrayRef jrefserialized=",out); + String[] oa = mapper.readValue(out, String[].class); + assertTrue(oa[0] instanceof String); + assertTrue(oa[1] instanceof String); + assertEquals(oa[0],oa[1]); + } + + @Test + void test2DObjectArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + Object o1 = new Object(); + Object o2 = o1; + Object[] arr1 = new Object[] { o1, o2 }; + Object[] arr2 = arr1; + String out = mapper.writeValueAsString(new Object[][] { arr1, arr2 }); + assertJRefCount(out, 2); + trace("test2DObjectArrayRef jrefserialized=",out); + Object[][] oa = mapper.readValue(out, Object[][].class); + assertTrue(oa[0][0] instanceof Map); + assertTrue(oa[0][1] instanceof Map); + assertEquals(oa[0], oa[1]); + assertArrayEquals(oa[0], oa[1]); + } + + @Test + void test2DStringArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String o1 = new String("one"); + String o2 = o1; + String[] arr1 = new String[] { o1, o2 }; + String[] arr2 = arr1; + String out = mapper.writeValueAsString(new String[][] { arr1, arr2 }); + assertJRefCount(out, 2); + trace("test2DStringArrayRef jrefserialized=",out); + String[][] oa = mapper.readValue(out, String[][].class); + assertTrue(oa[0][0] instanceof String); + assertTrue(oa[0][1] instanceof String); + assertEquals(oa[0], oa[1]); + assertArrayEquals(oa[0], oa[1]); + } + + @Test + void testIntegerArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + Integer o1 = Integer.valueOf(100); + Integer o2 = o1; + Integer[] arr = new Integer[] { o1, o2 }; + String out = mapper.writeValueAsString(arr); + assertJRefCount(out, 1); + trace("testIntegerArrayRef jrefserialized=",out); + Integer[] oa = mapper.readValue(out, Integer[].class); + assertTrue(oa[0] instanceof Integer); + assertTrue(oa[1] instanceof Integer); + assertEquals(oa[0],oa[1]); + } + + @Test + void test2DIntegerArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + Integer o1 = Integer.valueOf(5); + Integer o2 = o1; + Integer[] arr1 = new Integer[] { o1, o2 }; + Integer[] arr2 = arr1; + String out = mapper.writeValueAsString(new Integer[][] { arr1, arr2 }); + assertJRefCount(out, 2); + trace("test2DStringArrayRef jrefserialized=",out); + Integer[][] oa = mapper.readValue(out, Integer[][].class); + assertTrue(oa[0][0] instanceof Integer); + assertTrue(oa[0][1] instanceof Integer); + assertEquals(oa[0], oa[1]); + assertArrayEquals(oa[0], oa[1]); + } + + @Test + void test3DObjectArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + Object o1 = new Object(); + Object o2 = o1; + Object[] arr1 = new Object[] { o1, o2 }; + Object[][] arr2d = new Object[][] { arr1, arr1 }; + Object[][][] arr3d = new Object[][][] { arr2d, arr2d }; + + String out = mapper.writeValueAsString(arr3d); + assertJRefCount(out, 3); + trace("test3DObjectArrayRef jrefserialized=", out); + + Object[][][] oa = mapper.readValue(out, Object[][][].class); + assertEquals(oa[0], oa[1]); + assertEquals(oa[0][0], oa[0][1]); + assertEquals(oa[0][0][0], oa[0][0][1]); + assertTrue(oa[0][0][0] instanceof Map); + } + + @Test + void test3DStringArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String s1 = new String("test"); + String[] arr1 = new String[] { s1, s1 }; + String[][] arr2d = new String[][] { arr1, arr1 }; + String[][][] arr3d = new String[][][] { arr2d, arr2d }; + + String out = mapper.writeValueAsString(arr3d); + assertJRefCount(out, 3); + trace("test3DStringArrayRef jrefserialized=", out); + + String[][][] oa = mapper.readValue(out, String[][][].class); + assertArrayEquals(oa[0], oa[1]); + assertArrayEquals(oa[0][0], oa[0][1]); + assertEquals(oa[0][0][0], oa[0][0][1]); + assertTrue(oa[0][0][0] instanceof String); + } + + @Test + void test4DObjectArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + Object o1 = new Object(); + Object[] arr1 = new Object[] { o1 }; + Object[][] arr2 = new Object[][] { arr1 }; + Object[][][] arr3 = new Object[][][] { arr2 }; + Object[][][][] arr4 = new Object[][][][] { arr3, arr3 }; + + String out = mapper.writeValueAsString(arr4); + assertJRefCount(out, 1); + trace("test4DObjectArrayRef jrefserialized=", out); + + Object[][][][] oa = mapper.readValue(out, Object[][][][].class); + assertEquals(oa[0], oa[1]); + assertEquals(oa[0][0], oa[1][0]); + assertEquals(oa[0][0][0], oa[1][0][0]); + assertTrue(oa[0][0][0][0] instanceof Map); + } + + @Test + void test4DStringArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String s1 = new String("deep"); + String s2 = new String("thoughts"); + String[] arr1 = new String[] { s1, s2, s2 }; + String[] arr1a = arr1; + String[][] arr2 = new String[][] { arr1, arr1a }; + String[][] arr2a = arr2; + String[][][] arr3 = new String[][][] { arr2, arr2a }; + String[][][] arr3a = arr3; + String[][][][] arr4 = new String[][][][] { arr3, arr3a, arr3 }; + + String out = mapper.writeValueAsString(arr4); + assertJRefCount(out, 5); + trace("test4DStringArrayRef jrefserialized=", out); + + String[][][][] oa = mapper.readValue(out, String[][][][].class); + assertArrayEquals(oa[0], oa[1]); + assertArrayEquals(oa[0], oa[2]); + assertArrayEquals(oa[0][0], oa[0][1]); + assertArrayEquals(oa[0][0][0], oa[0][0][1]); + assertEquals(oa[0][0][0][0], "deep"); + assertEquals(oa[0][0][0][1], "thoughts"); + + } +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java b/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java new file mode 100644 index 0000000000..b78ffa1cbf --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java @@ -0,0 +1,288 @@ +package tools.jackson.databind.jref; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import tools.jackson.databind.ObjectMapper; + +public class JRefBeanNonPublicMemberDeserializerTest extends JRefAbstractTest { + + @Test + public void testMapRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + Object v2 = v1; + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + trace("testMapRefNoValueType jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + + @Test + public void testMapNoRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + Object v2 = new String("val1"); + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + trace("testMapNoRefNoValueType jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testStringListTwoValue() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + // These are two separate instances, with same string underneath + String s1 = new String("one"); + String s2 = new String("one"); + List sl = List.of(s1, s2); + String out = mapper.writeValueAsString(sl); + trace("testStringListTwoValue jrefserialized=",out); + List result = mapper.readValue(out, List.class); + assertEquals(result.get(0), s1); + assertEquals(result.get(1), s2); + } + + @Test + public void testStringListOneValue() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String s1 = new String("one"); + // s2 is reference to s1 + String s2 = s1; + List sl = List.of(s1, s2); + String out = mapper.writeValueAsString(sl); + trace("testStringListOneValue jrefserialized=",out); + List result = mapper.readValue(out, List.class); + assertEquals(result.get(0), s1); + assertEquals(result.get(1), s2); + } + + @Test + public void testIntList() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + List l = List.of(10,10); + String input = mapper.writeValueAsString(l); + trace("testIntegerList",input); + List result = mapper.readValue(input, List.class); + assertEquals(l, result); + + } + + static class IntType { + @JsonProperty + int i; + } + + static class IntItems { + @JsonProperty + int j; + @JsonProperty + List items; + } + + @Test + public void testIntItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"j\": 20, \"items\":[ { \"i\": 10}, { \"i\": { \"$ref\": \"#/items/0/i\" }}, { \"i\": { \"$ref\": \"#/j\" }}]}"; + IntItems result = mapper.readValue(input, IntItems.class); + assertEquals(result.items.get(0).i, result.items.get(1).i); + assertEquals(result.j, result.items.get(2).i); + } + + static class StringItems { + @JsonProperty + List items; + @JsonProperty + String second; + } + + @Test + public void testStringItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[\"hello\", { \"$ref\": \"#/items/0\" }], \"second\": { \"$ref\": \"#/items/0\" }}"; + StringItems result = mapper.readValue(input, StringItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + assertEquals(result.items.get(0), result.second); + } + + static class IntegerItems { + @JsonProperty + List items; + } + + @Test + public void testIntegerItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[5, { \"$ref\": \"#/items/0\" }]}"; + IntegerItems result = mapper.readValue(input, IntegerItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class DoubleItems { + @JsonProperty + List items; + } + + @Test + public void testDoubleItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; + DoubleItems result = mapper.readValue(input, DoubleItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class FloatItems { + @JsonProperty + List items; + } + + @Test + public void testFloatItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; + FloatItems result = mapper.readValue(input, FloatItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class BooleanItems { + @JsonProperty + List items; + } + + @Test + public void testBooleanItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[true, { \"$ref\": \"#/items/0\" }]}"; + BooleanItems result = mapper.readValue(input, BooleanItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + static class Human { + @JsonProperty + String name; + @JsonProperty + Human parent; + @JsonProperty + Map props; + @JsonProperty + Human o; + @JsonProperty + String otherName; + @JsonProperty + Map moreProps; + + public Human() { + } + + @Override + public String toString() { + return "Human[name=" + name + ", parent=" + parent + ", props=" + props + ", o=" + this.o + "]"; + } + + } + + static class Message { + @JsonProperty + List items; + + public Message(List items) { + this.items = items; + } + + @Override + public String toString() { + return "Message[items=" + items + "]"; + } + } + + @Test + public void testStringItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 }, \"otherName\": { \"$ref\": \"#/items/0/name\" } }]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0).name, msg.items.get(0).otherName); + } + + @Test + public void testCollectionStringKeyItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"$ref\": \"#/items/0\" }]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0), msg.items.get(1)); + } + + @Test + public void testCollectionObjectKeyItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"name\": \"wendy\", \"parent\": null, \"moreProps\": { \"$ref\": \"#/items/0/props\" }}]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0).props, msg.items.get(1).moreProps); + } + + @Test + public void testJRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + + Map m1 = Map.of("s1", 1); + Human sam = new Human(); + sam.name = "sam"; + sam.props = m1; + Map m2 = Map.of("q","r","p",sam); + Human wendy = new Human(); + wendy.name = "wendy"; + wendy.parent = sam; + wendy.props = m2; + Human rick = new Human(); + rick.name = "rick"; + rick.parent = sam; + rick.o = sam; + + // wendy and rick are the 2 items in message + Message mess = new Message(List.of(wendy, rick)); + + String gen = mapper.writeValueAsString(mess); + System.out.println("gen="+gen); + /* + String message = "{\r\n" + " \"items\" : [ {\r\n" + " \"name\" : \"wendy\",\r\n" + " \"parent\" : {\r\n" + + " \"name\" : \"sam\",\r\n" + " \"parent\" : null,\r\n" + " \"props\" : {\r\n" + + " \"s1\" : 1\r\n" + " }\r\n" + " },\r\n" + " \"props\" : {\r\n" + + " \"q\" : \"r\",\r\n" + " \"p\" : { \"$ref\" : \"#/items/0/parent\" }" + " }\r\n" + + " }, {\r\n" + " \"name\" : \"rick\",\r\n" + " \"parent\" : {\r\n" + + " \"$ref\" : \"#/items/0/parent\"\r\n" + " },\r\n" + + " \"o\" : { \"$ref\" : \"#/items/0/parent\" }\r\n" + " } ]\r\n" + "}"; + */ + // Now read + Message msg = mapper.readValue(gen, Message.class); + // Compare with structure expected + assertEquals(msg.items.size(), 2); + assertEquals(msg.items.get(0).parent, msg.items.get(0).props.get("p")); + assertEquals(msg.items.get(0).parent, msg.items.get(1).parent); + } + +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberTest.java b/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberTest.java new file mode 100644 index 0000000000..430b24fa96 --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberTest.java @@ -0,0 +1,182 @@ +package tools.jackson.databind.jref; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; + +public class JRefBeanPublicMemberTest extends JRefAbstractTest { + + static class StringItems { + public List items; + public String second; + } + + @Test + public void testStringItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[\"hello\", { \"$ref\": \"#/items/0\" }], \"second\": { \"$ref\": \"#/items/0\" }}"; + StringItems result = mapper.readValue(input, StringItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + assertEquals(result.items.get(0), result.second); + } + + static class IntegerItems { + public List items; + } + + @Test + public void testIntegerItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[5, { \"$ref\": \"#/items/0\" }]}"; + IntegerItems result = mapper.readValue(input, IntegerItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class DoubleItems { + public List items; + } + + @Test + public void testDoubleItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; + DoubleItems result = mapper.readValue(input, DoubleItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class FloatItems { + public List items; + } + + @Test + public void testFloatItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; + FloatItems result = mapper.readValue(input, FloatItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class BooleanItems { + public List items; + } + + @Test + public void testBooleanItems() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String input = "{\"items\":[true, { \"$ref\": \"#/items/0\" }]}"; + BooleanItems result = mapper.readValue(input, BooleanItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class Human { + public String name; + public Human parent; + public Map props; + public Human o; + public String otherName; + public Map moreProps; + + public Human() { + } + + @Override + public String toString() { + return "Human[name=" + name + ", parent=" + parent + ", props=" + props + ", o=" + this.o + "]"; + } + + } + + static class Message { + public List items; + + public Message(List items) { + this.items = items; + } + + @Override + public String toString() { + return "Message[items=" + items + "]"; + } + } + + @Test + public void testStringItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 }, \"otherName\": { \"$ref\": \"#/items/0/name\" } }]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0).name, msg.items.get(0).otherName); + } + + @Test + public void testCollectionStringKeyItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"$ref\": \"#/items/0\" }]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0), msg.items.get(1)); + } + + @Test + public void testCollectionObjectKeyItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"name\": \"wendy\", \"parent\": null, \"moreProps\": { \"$ref\": \"#/items/0/props\" }}]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0).props, msg.items.get(1).moreProps); + } + + @Test + public void testJRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + + Map m1 = Map.of("s1", 1); + Human sam = new Human(); + sam.name = "sam"; + sam.props = m1; + Map m2 = Map.of("q", "r", "p", sam); + Human wendy = new Human(); + wendy.name = "wendy"; + wendy.parent = sam; + wendy.props = m2; + Human rick = new Human(); + rick.name = "rick"; + rick.parent = sam; + rick.o = sam; + + // wendy and rick are the 2 items in message + Message mess = new Message(List.of(wendy, rick)); + + String gen = mapper.writeValueAsString(mess); + System.out.println("gen=" + gen); + /* + * String message = "{\r\n" + " \"items\" : [ {\r\n" + + * " \"name\" : \"wendy\",\r\n" + " \"parent\" : {\r\n" + + * " \"name\" : \"sam\",\r\n" + " \"parent\" : null,\r\n" + + * " \"props\" : {\r\n" + " \"s1\" : 1\r\n" + " }\r\n" + + * " },\r\n" + " \"props\" : {\r\n" + " \"q\" : \"r\",\r\n" + + * " \"p\" : { \"$ref\" : \"#/items/0/parent\" }" + " }\r\n" + + * " }, {\r\n" + " \"name\" : \"rick\",\r\n" + " \"parent\" : {\r\n" + + * " \"$ref\" : \"#/items/0/parent\"\r\n" + " },\r\n" + + * " \"o\" : { \"$ref\" : \"#/items/0/parent\" }\r\n" + " } ]\r\n" + "}"; + */ + // Now read + Message msg = mapper.readValue(gen, Message.class); + // Compare with structure expected + assertEquals(msg.items.size(), 2); + assertEquals(msg.items.get(0).parent, msg.items.get(0).props.get("p")); + assertEquals(msg.items.get(0).parent, msg.items.get(1).parent); + } + +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefCircularAndNestedReferenceTests.java b/src/test/java/tools/jackson/databind/jref/JRefCircularAndNestedReferenceTests.java new file mode 100644 index 0000000000..0e33b3de3f --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefCircularAndNestedReferenceTests.java @@ -0,0 +1,139 @@ +package tools.jackson.databind.jref; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import tools.jackson.core.exc.StreamConstraintsException; +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.ObjectMapper; + +public class JRefCircularAndNestedReferenceTests extends JRefAbstractTest { + + static class Node { + public String name; + public Node child; + public Node sibling; + public List neighbors = new ArrayList<>(); + + public Node() {} + + public Node(String name) { + this.name = name; + } + } + + static class Graph { + public Node root; + public List allNodes = new ArrayList<>(); + } + + @Test + public void testNestedCircularDeserialization() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + + // JSON representing a circular structure: root -> child -> child (ref to root) + String json = "{" + + " \"root\": {" + + " \"name\": \"parent\"," + + " \"child\": {" + + " \"name\": \"child\"," + + " \"child\": { \"$ref\": \"#/root\" }" + + " }" + + " }" + + "}"; + + try { + mapper.readValue(json, Graph.class); + fail(); + } catch (DatabindException e) { + // this should be thrown by deserialization + // so we pass + } + + } + + @Test + public void testSiblingAndNeighborDeserialization() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + + // JSON representing nodes where neighbors refer back to previous nodes in a list + String json = "{" + + " \"allNodes\": [" + + " { \"name\": \"node0\" }," + + " { \"name\": \"node1\", \"sibling\": { \"$ref\": \"#/allNodes/0\" } }," + + " { \"name\": \"node2\", \"neighbors\": [ { \"$ref\": \"#/allNodes/0\" }, { \"$ref\": \"#/allNodes/1\" } ] }" + + " ]" + + "}"; + + Graph graph = mapper.readValue(json, Graph.class); + + assertEquals(3, graph.allNodes.size()); + Node n0 = graph.allNodes.get(0); + Node n1 = graph.allNodes.get(1); + Node n2 = graph.allNodes.get(2); + + assertEquals("node0", n0.name); + assertEquals("node1", n1.name); + assertEquals("node2", n2.name); + + assertSame(n0, n1.sibling); + assertEquals(2, n2.neighbors.size()); + assertSame(n0, n2.neighbors.get(0)); + assertSame(n1, n2.neighbors.get(1)); + } + + @Test + public void testCircularStructureSerialization() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + + Node root = new Node("root"); + Node child = new Node("child"); + root.child = child; + child.child = root; // Circular + + Graph graph = new Graph(); + graph.root = root; + graph.allNodes.add(root); + graph.allNodes.add(child); + + try { + mapper.writeValueAsString(graph); + fail(); + } catch (StreamConstraintsException e) { + // this should be thrown by serialization + // so we pass + } + } + + @Test + public void testNestedPathDeserialization() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + + // Test resolving a path that goes through multiple levels of objects and arrays + String json = "{" + + " \"root\": {" + + " \"neighbors\": [" + + " { \"name\": \"neighbor0\", \"child\": { \"name\": \"inner\" } }" + + " ]" + + " }," + + " \"allNodes\": [" + + " { \"$ref\": \"#/root/neighbors/name/child/name\" }" + + " ]" + + "}"; + + try { + mapper.readValue(json, Graph.class); + fail(); + } catch (DatabindException e) { + // this should be thrown by deserialization + // so we pass + System.out.println(e); + } + } +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefMapTest.java b/src/test/java/tools/jackson/databind/jref/JRefMapTest.java new file mode 100644 index 0000000000..12266f273e --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefMapTest.java @@ -0,0 +1,186 @@ +package tools.jackson.databind.jref; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; + +public class JRefMapTest extends JRefAbstractTest { + + @Test + public void testMapKeyNoRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + String v2 = new String("first"); + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 0); + trace("testMapKeyNoRef jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapKeyRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first key, but serialization treats + // it like separate string, since map keys cannot be jrefs + String v2 = k1; + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + // Because the reference is a key, it should not be serialized to jref + // so count is expected to be 0 + assertJRefCount(out, 0); + trace("testMapKeyRef jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueNoRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + String v2 = new String("val1"); + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 1); + trace("testMapValueNoRef jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + String v2 = v1; + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 1); + trace("testMapValueRef jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueMultRef() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + String v2 = v1; + String k3 = "third"; + // third value refs first + String v3 = v1; + String k4 = "fourth"; + String v4 = v1; + Map mi = Map.of(k1,v1,k2,v2,k3,v3,k4,v4); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 3); + trace("testMapValueMultRef jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapKeyNoRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + Object v2 = new String("first"); + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 0); + trace("testMapKeyNoRefNoValueType jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapKeyRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first key, but serialization treats + // it like separate string, since map keys cannot be jrefs + Object v2 = k1; + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + // Because the reference is a key, it should not be serialized to jref + // so count is expected to be 0 + assertJRefCount(out, 0); + trace("testMapKeyRefNoValueType jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueNoRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + Object v2 = new String("val1"); + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 1); + trace("testMapValueNoRefNoValueType jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + + @Test + public void testMapValueRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + Object v2 = v1; + Map mi = Map.of(k1,v1,k2,v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 1); + trace("testMapValueRefNoValueType jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueMultRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + Object v2 = v1; + String k3 = "third"; + // third value refs first + Object v3 = v1; + String k4 = "fourth"; + Object v4 = v1; + Map mi = Map.of(k1,v1,k2,v2,k3,v3,k4,v4); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 3); + trace("testMapValueMultRefNoValueType jrefserialized=",out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java b/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java new file mode 100644 index 0000000000..d4ab97d5c9 --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java @@ -0,0 +1,100 @@ +package tools.jackson.databind.jref; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import tools.jackson.databind.ObjectMapper; + +public class JRefRecordBeanTest extends JRefAbstractTest { + + static record IntBean(Integer j) { + } + + @Test + public void testIntBeanArray() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + IntBean i1 = new IntBean(10); + IntBean i2 = new IntBean(20); + IntBean[] beans = new IntBean[] { i1, i2, i1, i2}; + String out = mapper.writeValueAsString(beans); + trace("testIntBeanArray", out); + IntBean[] result = mapper.readValue(out, IntBean[].class); + assertEquals(result[0], result[2]); + assertEquals(result[1], result[3]); + } + + @Test + public void testIntBeanList() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + IntBean i1 = new IntBean(10); + IntBean i2 = new IntBean(20); + List beans = List.of(i1, i2, i1, i2); + String out = mapper.writeValueAsString(beans); + trace("testIntBeanList", out); + @SuppressWarnings("unchecked") + List result = (List) mapper.readValue(out, List.class); + assertEquals(result.get(0), result.get(2)); + assertEquals(result.get(1), result.get(3)); + } + + record StringKeyBeanMap(Map items) { + } + + @Test + public void testStringKeyMap() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + IntBean i1 = new IntBean(10); + IntBean i2 = new IntBean(20); + StringKeyBeanMap beanMap = new StringKeyBeanMap(Map.of("one",i1,"two",i2,"three",i1,"four",i2)); + String out = mapper.writeValueAsString(beanMap); + trace("testIntBeanStringKeyMap", out); + StringKeyBeanMap result = mapper.readValue(out, StringKeyBeanMap.class); + assertEquals(result.items().get("one"), result.items().get("three")); + assertEquals(result.items().get("two"), result.items().get("four")); + } + + record Node(@JsonProperty Node parent, @JsonProperty String name) { + } + + record NodeList(@JsonProperty List nodes) { + + } + @Test + public void testNodeTree() throws Exception { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + Node root = new Node(null,"root"); + String[] nodeNames = new String[] {"child1","child2","child3"}; + List nodeList = new ArrayList<>(); + for(int i =0; i< nodeNames.length; i++) { + nodeList.add(new Node(root,nodeNames[i])); + } + // Add references to previously added nodes in reverse order + nodeList.add(nodeList.get(2)); + nodeList.add(nodeList.get(1)); + nodeList.add(nodeList.get(0)); + String out = mapper.writeValueAsString(new NodeList(nodeList)); + trace("testNodeTree", out); + NodeList result = mapper.readValue(out, NodeList.class); + List nodes = result.nodes(); + // assert nodes list same as input + assertEquals(nodeList.size(),nodes.size()); + for(int firstIndex = 0; firstIndex < nodeList.size() /2; firstIndex++) { + int secondIndex = (nodeList.size() - 1) - firstIndex; + Node n1 = result.nodes().get(firstIndex); + assertEquals(root.name(),n1.parent().name()); + Node n2 = result.nodes().get(secondIndex); + assertEquals(root.name(),n2.parent().name()); + assertEquals(n1,n2); + + } + } + + +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefSimpleNestedTypeTest.java b/src/test/java/tools/jackson/databind/jref/JRefSimpleNestedTypeTest.java new file mode 100644 index 0000000000..b7766d879f --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefSimpleNestedTypeTest.java @@ -0,0 +1,76 @@ +package tools.jackson.databind.jref; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; + +public class JRefSimpleNestedTypeTest extends JRefAbstractTest { + + record TreeNode(TreeNode parent, String name, String data) { + } + private static final String ADDRESS = "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\r\n" + + "\r\n" + + "Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\r\n" + + "\r\n" + + "But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth."; + + TreeNode[] buildTwoLevelThreeChildrenArray() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode thirdChild = new TreeNode(topNode, "child3" , "data3"); + // Put top and all nodes in array + return new TreeNode[] { topNode, firstChild, secondChild, thirdChild }; + } + @Test + void testSerializeSingleLevelTreeThreeChildrenNoJRef() { + + ObjectMapper mapper = buildObjectMapperWithoutJRefSupport(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(buildTwoLevelThreeChildrenArray()); + // No jrefs + assertJRefCount(json, 0); + } + + @Test + void testSerializeSingleLevelTreeThreeChildrenJRef() { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(buildTwoLevelThreeChildrenArray()); + assertJRefCount(json, 3); + } + + static String JREF_JSON = "[ {\r\n" + + " \"parent\" : null,\r\n" + + " \"name\" : \"top\",\r\n" + + " \"data\" : \"Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\\r\\n\\r\\nNow we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\\r\\n\\r\\nBut, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth.\"\r\n" + + "}, {\r\n" + + " \"parent\" : {\r\n" + + " \"$ref\" : \"#/0\"\r\n" + + " },\r\n" + + " \"name\" : \"child1\",\r\n" + + " \"data\" : \"data1\"\r\n" + + "}, {\r\n" + + " \"parent\" : {\r\n" + + " \"$ref\" : \"#/0\"\r\n" + + " },\r\n" + + " \"name\" : \"child2\",\r\n" + + " \"data\" : \"data2\"\r\n" + + "}, {\r\n" + + " \"parent\" : {\r\n" + + " \"$ref\" : \"#/0\"\r\n" + + " },\r\n" + + " \"name\" : \"child3\",\r\n" + + " \"data\" : \"data3\"\r\n" + + "} ]"; + @Test + void testDeerializeSingleLevelTreeThreeChildrenJRef() { + ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + TreeNode[] nodes = mapper.readValue(JREF_JSON, TreeNode[].class); + assertEquals(nodes[0],nodes[1].parent); + assertEquals(nodes[0],nodes[2].parent); + assertEquals(nodes[0],nodes[3].parent); + } + +} From a2d1a16a93113eacbe9cae28cb587fff1f1e30b8 Mon Sep 17 00:00:00 2001 From: Scott Lewis Date: Mon, 22 Jun 2026 13:29:16 -0700 Subject: [PATCH 2/8] Re-factored tests (only) for coverage, removed overlap, updated names for clarity. --- .../databind/jref/JRefAbstractTest.java | 40 +-- .../jackson/databind/jref/JRefArrayTest.java | 66 ++--- ...efBeanNonPublicMemberDeserializerTest.java | 95 +++---- ...JRefBeanPublicMemberDeserializerTest.java} | 64 +---- .../JRefCircularAndNestedReferenceTests.java | 139 ---------- .../jref/JRefCircularReferenceTests.java | 124 +++++++++ .../jackson/databind/jref/JRefMapTest.java | 91 ++++--- .../databind/jref/JRefNestedTypeTest.java | 242 ++++++++++++++++++ .../databind/jref/JRefRecordBeanTest.java | 42 +-- .../jref/JRefSimpleNestedTypeTest.java | 76 ------ 10 files changed, 530 insertions(+), 449 deletions(-) rename src/test/java/tools/jackson/databind/jref/{JRefBeanPublicMemberTest.java => JRefBeanPublicMemberDeserializerTest.java} (64%) delete mode 100644 src/test/java/tools/jackson/databind/jref/JRefCircularAndNestedReferenceTests.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefCircularReferenceTests.java create mode 100644 src/test/java/tools/jackson/databind/jref/JRefNestedTypeTest.java delete mode 100644 src/test/java/tools/jackson/databind/jref/JRefSimpleNestedTypeTest.java diff --git a/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java b/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java index f8c529fa82..75c1ec5ead 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java @@ -1,38 +1,44 @@ package tools.jackson.databind.jref; -import static org.junit.Assert.assertEquals; -import static tools.jackson.databind.testutil.DatabindTestUtil.jsonMapperBuilder; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.regex.Pattern; import tools.jackson.databind.JRefModule; import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; public class JRefAbstractTest { - protected ObjectMapper buildObjectMapperWithJRefSupport() { + public static boolean TRACE = false; + + public static JsonMapper.Builder jsonMapperBuilder() { + return JsonMapper.builder(); + } + + protected ObjectMapper buildObjectMapperJRef() { return jsonMapperBuilder().addModule(new JRefModule()).build(); } - protected ObjectMapper buildObjectMapperWithoutJRefSupport() { + protected ObjectMapper buildObjectMapperNoJRef() { return jsonMapperBuilder().build(); } - static long countMatches(String text, String target) { - if (text == null || target == null || target.isEmpty()) return 0; - String quotedTarget = Pattern.quote(target); - - return Pattern.compile(quotedTarget) - .matcher(text) - .results() - .count(); - } - + static long countMatches(String text, String target) { + if (text == null || target == null || target.isEmpty()) + return 0; + String quotedTarget = Pattern.quote(target); + + return Pattern.compile(quotedTarget).matcher(text).results().count(); + } + protected void assertJRefCount(String input, long expectedJRefs) { - assertEquals(expectedJRefs, countMatches(input,"$ref")); + assertEquals(expectedJRefs, countMatches(input, "$ref")); } - + void trace(String method, String s) { - System.out.println(method+"."+s); + if (TRACE) { + System.out.println(method + "." + s); + } } } diff --git a/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java b/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java index b9885dca47..ca7637841a 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java @@ -1,57 +1,57 @@ package tools.jackson.databind.jref; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Map; -import org.junit.jupiter.api.Test; - import tools.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + public class JRefArrayTest extends JRefAbstractTest { @Test void testObjectArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); Object o1 = new Object(); Object o2 = o1; Object[] arr = new Object[] { o1, o2 }; String out = mapper.writeValueAsString(arr); assertJRefCount(out, 1); - trace("testObjectArrayRef jrefserialized=",out); + trace("testObjectArrayRef jrefserialized=", out); Object[] oa = mapper.readValue(out, Object[].class); assertTrue(oa[0] instanceof Map); assertTrue(oa[1] instanceof Map); - assertEquals(oa[0],oa[1]); + assertEquals(oa[0], oa[1]); } - + @Test void testStringArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String o1 = new String("one"); String o2 = o1; String[] arr = new String[] { o1, o2 }; String out = mapper.writeValueAsString(arr); assertJRefCount(out, 1); - trace("testStringArrayRef jrefserialized=",out); + trace("testStringArrayRef jrefserialized=", out); String[] oa = mapper.readValue(out, String[].class); assertTrue(oa[0] instanceof String); assertTrue(oa[1] instanceof String); - assertEquals(oa[0],oa[1]); + assertEquals(oa[0], oa[1]); } @Test void test2DObjectArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); Object o1 = new Object(); Object o2 = o1; Object[] arr1 = new Object[] { o1, o2 }; Object[] arr2 = arr1; String out = mapper.writeValueAsString(new Object[][] { arr1, arr2 }); assertJRefCount(out, 2); - trace("test2DObjectArrayRef jrefserialized=",out); + trace("test2DObjectArrayRef jrefserialized=", out); Object[][] oa = mapper.readValue(out, Object[][].class); assertTrue(oa[0][0] instanceof Map); assertTrue(oa[0][1] instanceof Map); @@ -61,14 +61,14 @@ void test2DObjectArrayRef() throws Exception { @Test void test2DStringArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String o1 = new String("one"); String o2 = o1; String[] arr1 = new String[] { o1, o2 }; String[] arr2 = arr1; String out = mapper.writeValueAsString(new String[][] { arr1, arr2 }); assertJRefCount(out, 2); - trace("test2DStringArrayRef jrefserialized=",out); + trace("test2DStringArrayRef jrefserialized=", out); String[][] oa = mapper.readValue(out, String[][].class); assertTrue(oa[0][0] instanceof String); assertTrue(oa[0][1] instanceof String); @@ -78,29 +78,29 @@ void test2DStringArrayRef() throws Exception { @Test void testIntegerArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); Integer o1 = Integer.valueOf(100); Integer o2 = o1; Integer[] arr = new Integer[] { o1, o2 }; String out = mapper.writeValueAsString(arr); assertJRefCount(out, 1); - trace("testIntegerArrayRef jrefserialized=",out); + trace("testIntegerArrayRef jrefserialized=", out); Integer[] oa = mapper.readValue(out, Integer[].class); assertTrue(oa[0] instanceof Integer); assertTrue(oa[1] instanceof Integer); - assertEquals(oa[0],oa[1]); + assertEquals(oa[0], oa[1]); } - + @Test void test2DIntegerArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); Integer o1 = Integer.valueOf(5); Integer o2 = o1; Integer[] arr1 = new Integer[] { o1, o2 }; Integer[] arr2 = arr1; String out = mapper.writeValueAsString(new Integer[][] { arr1, arr2 }); assertJRefCount(out, 2); - trace("test2DStringArrayRef jrefserialized=",out); + trace("test2DStringArrayRef jrefserialized=", out); Integer[][] oa = mapper.readValue(out, Integer[][].class); assertTrue(oa[0][0] instanceof Integer); assertTrue(oa[0][1] instanceof Integer); @@ -110,17 +110,17 @@ void test2DIntegerArrayRef() throws Exception { @Test void test3DObjectArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); Object o1 = new Object(); Object o2 = o1; Object[] arr1 = new Object[] { o1, o2 }; Object[][] arr2d = new Object[][] { arr1, arr1 }; Object[][][] arr3d = new Object[][][] { arr2d, arr2d }; - + String out = mapper.writeValueAsString(arr3d); assertJRefCount(out, 3); trace("test3DObjectArrayRef jrefserialized=", out); - + Object[][][] oa = mapper.readValue(out, Object[][][].class); assertEquals(oa[0], oa[1]); assertEquals(oa[0][0], oa[0][1]); @@ -130,16 +130,16 @@ void test3DObjectArrayRef() throws Exception { @Test void test3DStringArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String s1 = new String("test"); String[] arr1 = new String[] { s1, s1 }; String[][] arr2d = new String[][] { arr1, arr1 }; String[][][] arr3d = new String[][][] { arr2d, arr2d }; - + String out = mapper.writeValueAsString(arr3d); assertJRefCount(out, 3); trace("test3DStringArrayRef jrefserialized=", out); - + String[][][] oa = mapper.readValue(out, String[][][].class); assertArrayEquals(oa[0], oa[1]); assertArrayEquals(oa[0][0], oa[0][1]); @@ -149,17 +149,17 @@ void test3DStringArrayRef() throws Exception { @Test void test4DObjectArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); Object o1 = new Object(); Object[] arr1 = new Object[] { o1 }; Object[][] arr2 = new Object[][] { arr1 }; Object[][][] arr3 = new Object[][][] { arr2 }; Object[][][][] arr4 = new Object[][][][] { arr3, arr3 }; - + String out = mapper.writeValueAsString(arr4); assertJRefCount(out, 1); trace("test4DObjectArrayRef jrefserialized=", out); - + Object[][][][] oa = mapper.readValue(out, Object[][][][].class); assertEquals(oa[0], oa[1]); assertEquals(oa[0][0], oa[1][0]); @@ -169,7 +169,7 @@ void test4DObjectArrayRef() throws Exception { @Test void test4DStringArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String s1 = new String("deep"); String s2 = new String("thoughts"); String[] arr1 = new String[] { s1, s2, s2 }; @@ -179,11 +179,11 @@ void test4DStringArrayRef() throws Exception { String[][][] arr3 = new String[][][] { arr2, arr2a }; String[][][] arr3a = arr3; String[][][][] arr4 = new String[][][][] { arr3, arr3a, arr3 }; - + String out = mapper.writeValueAsString(arr4); assertJRefCount(out, 5); trace("test4DStringArrayRef jrefserialized=", out); - + String[][][][] oa = mapper.readValue(out, String[][][][].class); assertArrayEquals(oa[0], oa[1]); assertArrayEquals(oa[0], oa[2]); diff --git a/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java b/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java index b78ffa1cbf..9b35f2079c 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java @@ -16,44 +16,43 @@ public class JRefBeanNonPublicMemberDeserializerTest extends JRefAbstractTest { @Test public void testMapRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); Object v1 = new String("val1"); String k2 = new String("second"); // second value refs first Object v2 = v1; - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); - trace("testMapRefNoValueType jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } - @Test public void testMapNoRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); Object v1 = new String("val1"); String k2 = new String("second"); // second value refs first Object v2 = new String("val1"); - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); - trace("testMapNoRefNoValueType jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapNoRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } @Test public void testStringListTwoValue() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); // These are two separate instances, with same string underneath String s1 = new String("one"); String s2 = new String("one"); List sl = List.of(s1, s2); String out = mapper.writeValueAsString(sl); - trace("testStringListTwoValue jrefserialized=",out); + trace("testStringListTwoValue jrefserialized=", out); List result = mapper.readValue(out, List.class); assertEquals(result.get(0), s1); assertEquals(result.get(1), s2); @@ -61,13 +60,13 @@ public void testStringListTwoValue() throws Exception { @Test public void testStringListOneValue() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String s1 = new String("one"); // s2 is reference to s1 String s2 = s1; List sl = List.of(s1, s2); String out = mapper.writeValueAsString(sl); - trace("testStringListOneValue jrefserialized=",out); + trace("testStringListOneValue jrefserialized=", out); List result = mapper.readValue(out, List.class); assertEquals(result.get(0), s1); assertEquals(result.get(1), s2); @@ -75,13 +74,13 @@ public void testStringListOneValue() throws Exception { @Test public void testIntList() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - List l = List.of(10,10); + ObjectMapper mapper = buildObjectMapperJRef(); + List l = List.of(10, 10); String input = mapper.writeValueAsString(l); - trace("testIntegerList",input); + trace("testIntegerList", input); List result = mapper.readValue(input, List.class); assertEquals(l, result); - + } static class IntType { @@ -98,7 +97,7 @@ static class IntItems { @Test public void testIntItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"j\": 20, \"items\":[ { \"i\": 10}, { \"i\": { \"$ref\": \"#/items/0/i\" }}, { \"i\": { \"$ref\": \"#/j\" }}]}"; IntItems result = mapper.readValue(input, IntItems.class); assertEquals(result.items.get(0).i, result.items.get(1).i); @@ -114,7 +113,7 @@ static class StringItems { @Test public void testStringItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[\"hello\", { \"$ref\": \"#/items/0\" }], \"second\": { \"$ref\": \"#/items/0\" }}"; StringItems result = mapper.readValue(input, StringItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -128,7 +127,7 @@ static class IntegerItems { @Test public void testIntegerItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[5, { \"$ref\": \"#/items/0\" }]}"; IntegerItems result = mapper.readValue(input, IntegerItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -141,7 +140,7 @@ static class DoubleItems { @Test public void testDoubleItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; DoubleItems result = mapper.readValue(input, DoubleItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -154,7 +153,7 @@ static class FloatItems { @Test public void testFloatItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; FloatItems result = mapper.readValue(input, FloatItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -167,7 +166,7 @@ static class BooleanItems { @Test public void testBooleanItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[true, { \"$ref\": \"#/items/0\" }]}"; BooleanItems result = mapper.readValue(input, BooleanItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -202,6 +201,10 @@ static class Message { @JsonProperty List items; + public Message() { + + } + public Message(List items) { this.items = items; } @@ -214,7 +217,7 @@ public String toString() { @Test public void testStringItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); // Input has first item in Message.items list fully defined, and second item // jrefs to first item String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 }, \"otherName\": { \"$ref\": \"#/items/0/name\" } }]}"; @@ -225,7 +228,7 @@ public void testStringItemPath() throws Exception { @Test public void testCollectionStringKeyItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); // Input has first item in Message.items list fully defined, and second item // jrefs to first item String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"$ref\": \"#/items/0\" }]}"; @@ -236,7 +239,7 @@ public void testCollectionStringKeyItemPath() throws Exception { @Test public void testCollectionObjectKeyItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); // Input has first item in Message.items list fully defined, and second item // jrefs to first item String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"name\": \"wendy\", \"parent\": null, \"moreProps\": { \"$ref\": \"#/items/0/props\" }}]}"; @@ -245,44 +248,4 @@ public void testCollectionObjectKeyItemPath() throws Exception { assertEquals(msg.items.get(0).props, msg.items.get(1).moreProps); } - @Test - public void testJRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - - Map m1 = Map.of("s1", 1); - Human sam = new Human(); - sam.name = "sam"; - sam.props = m1; - Map m2 = Map.of("q","r","p",sam); - Human wendy = new Human(); - wendy.name = "wendy"; - wendy.parent = sam; - wendy.props = m2; - Human rick = new Human(); - rick.name = "rick"; - rick.parent = sam; - rick.o = sam; - - // wendy and rick are the 2 items in message - Message mess = new Message(List.of(wendy, rick)); - - String gen = mapper.writeValueAsString(mess); - System.out.println("gen="+gen); - /* - String message = "{\r\n" + " \"items\" : [ {\r\n" + " \"name\" : \"wendy\",\r\n" + " \"parent\" : {\r\n" - + " \"name\" : \"sam\",\r\n" + " \"parent\" : null,\r\n" + " \"props\" : {\r\n" - + " \"s1\" : 1\r\n" + " }\r\n" + " },\r\n" + " \"props\" : {\r\n" - + " \"q\" : \"r\",\r\n" + " \"p\" : { \"$ref\" : \"#/items/0/parent\" }" + " }\r\n" - + " }, {\r\n" + " \"name\" : \"rick\",\r\n" + " \"parent\" : {\r\n" - + " \"$ref\" : \"#/items/0/parent\"\r\n" + " },\r\n" - + " \"o\" : { \"$ref\" : \"#/items/0/parent\" }\r\n" + " } ]\r\n" + "}"; - */ - // Now read - Message msg = mapper.readValue(gen, Message.class); - // Compare with structure expected - assertEquals(msg.items.size(), 2); - assertEquals(msg.items.get(0).parent, msg.items.get(0).props.get("p")); - assertEquals(msg.items.get(0).parent, msg.items.get(1).parent); - } - } diff --git a/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberTest.java b/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberDeserializerTest.java similarity index 64% rename from src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberTest.java rename to src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberDeserializerTest.java index 430b24fa96..e772bea104 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberDeserializerTest.java @@ -9,7 +9,7 @@ import tools.jackson.databind.ObjectMapper; -public class JRefBeanPublicMemberTest extends JRefAbstractTest { +public class JRefBeanPublicMemberDeserializerTest extends JRefAbstractTest { static class StringItems { public List items; @@ -18,7 +18,7 @@ static class StringItems { @Test public void testStringItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[\"hello\", { \"$ref\": \"#/items/0\" }], \"second\": { \"$ref\": \"#/items/0\" }}"; StringItems result = mapper.readValue(input, StringItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -31,7 +31,7 @@ static class IntegerItems { @Test public void testIntegerItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[5, { \"$ref\": \"#/items/0\" }]}"; IntegerItems result = mapper.readValue(input, IntegerItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -43,7 +43,7 @@ static class DoubleItems { @Test public void testDoubleItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; DoubleItems result = mapper.readValue(input, DoubleItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -55,7 +55,7 @@ static class FloatItems { @Test public void testFloatItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; FloatItems result = mapper.readValue(input, FloatItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -67,7 +67,7 @@ static class BooleanItems { @Test public void testBooleanItems() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String input = "{\"items\":[true, { \"$ref\": \"#/items/0\" }]}"; BooleanItems result = mapper.readValue(input, BooleanItems.class); assertEquals(result.items.get(0), result.items.get(1)); @@ -94,6 +94,10 @@ public String toString() { static class Message { public List items; + public Message() { + + } + public Message(List items) { this.items = items; } @@ -106,7 +110,7 @@ public String toString() { @Test public void testStringItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); // Input has first item in Message.items list fully defined, and second item // jrefs to first item String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 }, \"otherName\": { \"$ref\": \"#/items/0/name\" } }]}"; @@ -117,7 +121,7 @@ public void testStringItemPath() throws Exception { @Test public void testCollectionStringKeyItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); // Input has first item in Message.items list fully defined, and second item // jrefs to first item String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"$ref\": \"#/items/0\" }]}"; @@ -128,7 +132,7 @@ public void testCollectionStringKeyItemPath() throws Exception { @Test public void testCollectionObjectKeyItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); // Input has first item in Message.items list fully defined, and second item // jrefs to first item String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"name\": \"wendy\", \"parent\": null, \"moreProps\": { \"$ref\": \"#/items/0/props\" }}]}"; @@ -137,46 +141,4 @@ public void testCollectionObjectKeyItemPath() throws Exception { assertEquals(msg.items.get(0).props, msg.items.get(1).moreProps); } - @Test - public void testJRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - - Map m1 = Map.of("s1", 1); - Human sam = new Human(); - sam.name = "sam"; - sam.props = m1; - Map m2 = Map.of("q", "r", "p", sam); - Human wendy = new Human(); - wendy.name = "wendy"; - wendy.parent = sam; - wendy.props = m2; - Human rick = new Human(); - rick.name = "rick"; - rick.parent = sam; - rick.o = sam; - - // wendy and rick are the 2 items in message - Message mess = new Message(List.of(wendy, rick)); - - String gen = mapper.writeValueAsString(mess); - System.out.println("gen=" + gen); - /* - * String message = "{\r\n" + " \"items\" : [ {\r\n" + - * " \"name\" : \"wendy\",\r\n" + " \"parent\" : {\r\n" + - * " \"name\" : \"sam\",\r\n" + " \"parent\" : null,\r\n" + - * " \"props\" : {\r\n" + " \"s1\" : 1\r\n" + " }\r\n" + - * " },\r\n" + " \"props\" : {\r\n" + " \"q\" : \"r\",\r\n" + - * " \"p\" : { \"$ref\" : \"#/items/0/parent\" }" + " }\r\n" + - * " }, {\r\n" + " \"name\" : \"rick\",\r\n" + " \"parent\" : {\r\n" + - * " \"$ref\" : \"#/items/0/parent\"\r\n" + " },\r\n" + - * " \"o\" : { \"$ref\" : \"#/items/0/parent\" }\r\n" + " } ]\r\n" + "}"; - */ - // Now read - Message msg = mapper.readValue(gen, Message.class); - // Compare with structure expected - assertEquals(msg.items.size(), 2); - assertEquals(msg.items.get(0).parent, msg.items.get(0).props.get("p")); - assertEquals(msg.items.get(0).parent, msg.items.get(1).parent); - } - } diff --git a/src/test/java/tools/jackson/databind/jref/JRefCircularAndNestedReferenceTests.java b/src/test/java/tools/jackson/databind/jref/JRefCircularAndNestedReferenceTests.java deleted file mode 100644 index 0e33b3de3f..0000000000 --- a/src/test/java/tools/jackson/databind/jref/JRefCircularAndNestedReferenceTests.java +++ /dev/null @@ -1,139 +0,0 @@ -package tools.jackson.databind.jref; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import tools.jackson.core.exc.StreamConstraintsException; -import tools.jackson.databind.DatabindException; -import tools.jackson.databind.ObjectMapper; - -public class JRefCircularAndNestedReferenceTests extends JRefAbstractTest { - - static class Node { - public String name; - public Node child; - public Node sibling; - public List neighbors = new ArrayList<>(); - - public Node() {} - - public Node(String name) { - this.name = name; - } - } - - static class Graph { - public Node root; - public List allNodes = new ArrayList<>(); - } - - @Test - public void testNestedCircularDeserialization() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - - // JSON representing a circular structure: root -> child -> child (ref to root) - String json = "{" + - " \"root\": {" + - " \"name\": \"parent\"," + - " \"child\": {" + - " \"name\": \"child\"," + - " \"child\": { \"$ref\": \"#/root\" }" + - " }" + - " }" + - "}"; - - try { - mapper.readValue(json, Graph.class); - fail(); - } catch (DatabindException e) { - // this should be thrown by deserialization - // so we pass - } - - } - - @Test - public void testSiblingAndNeighborDeserialization() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - - // JSON representing nodes where neighbors refer back to previous nodes in a list - String json = "{" + - " \"allNodes\": [" + - " { \"name\": \"node0\" }," + - " { \"name\": \"node1\", \"sibling\": { \"$ref\": \"#/allNodes/0\" } }," + - " { \"name\": \"node2\", \"neighbors\": [ { \"$ref\": \"#/allNodes/0\" }, { \"$ref\": \"#/allNodes/1\" } ] }" + - " ]" + - "}"; - - Graph graph = mapper.readValue(json, Graph.class); - - assertEquals(3, graph.allNodes.size()); - Node n0 = graph.allNodes.get(0); - Node n1 = graph.allNodes.get(1); - Node n2 = graph.allNodes.get(2); - - assertEquals("node0", n0.name); - assertEquals("node1", n1.name); - assertEquals("node2", n2.name); - - assertSame(n0, n1.sibling); - assertEquals(2, n2.neighbors.size()); - assertSame(n0, n2.neighbors.get(0)); - assertSame(n1, n2.neighbors.get(1)); - } - - @Test - public void testCircularStructureSerialization() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - - Node root = new Node("root"); - Node child = new Node("child"); - root.child = child; - child.child = root; // Circular - - Graph graph = new Graph(); - graph.root = root; - graph.allNodes.add(root); - graph.allNodes.add(child); - - try { - mapper.writeValueAsString(graph); - fail(); - } catch (StreamConstraintsException e) { - // this should be thrown by serialization - // so we pass - } - } - - @Test - public void testNestedPathDeserialization() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - - // Test resolving a path that goes through multiple levels of objects and arrays - String json = "{" + - " \"root\": {" + - " \"neighbors\": [" + - " { \"name\": \"neighbor0\", \"child\": { \"name\": \"inner\" } }" + - " ]" + - " }," + - " \"allNodes\": [" + - " { \"$ref\": \"#/root/neighbors/name/child/name\" }" + - " ]" + - "}"; - - try { - mapper.readValue(json, Graph.class); - fail(); - } catch (DatabindException e) { - // this should be thrown by deserialization - // so we pass - System.out.println(e); - } - } -} diff --git a/src/test/java/tools/jackson/databind/jref/JRefCircularReferenceTests.java b/src/test/java/tools/jackson/databind/jref/JRefCircularReferenceTests.java new file mode 100644 index 0000000000..c288ded311 --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefCircularReferenceTests.java @@ -0,0 +1,124 @@ +package tools.jackson.databind.jref; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import tools.jackson.core.exc.StreamConstraintsException; +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.ObjectMapper; + +public class JRefCircularReferenceTests extends JRefAbstractTest { + + static class Node { + public String name; + public Node child; + public Node sibling; + public List neighbors = new ArrayList<>(); + + public Node() { + } + + public Node(String name) { + this.name = name; + } + } + + static class Graph { + public Node root; + public List allNodes = new ArrayList<>(); + } + + @Test + public void testNestedCircularDeserialization() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + + // JSON representing a circular structure: root -> child -> child (ref to root) + String json = "{" + " \"root\": {" + " \"name\": \"parent\"," + " \"child\": {" + + " \"name\": \"child\"," + " \"child\": { \"$ref\": \"#/root\" }" + " }" + " }" + "}"; + + try { + mapper.readValue(json, Graph.class); + fail(); + } catch (DatabindException e) { + // this should be thrown by deserialization + // so we pass + } + + } + + @Test + public void testSiblingAndNeighborDeserialization() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + + // JSON representing nodes where neighbors refer back to previous nodes in a + // list + String json = "{" + " \"allNodes\": [" + " { \"name\": \"node0\" }," + + " { \"name\": \"node1\", \"sibling\": { \"$ref\": \"#/allNodes/0\" } }," + + " { \"name\": \"node2\", \"neighbors\": [ { \"$ref\": \"#/allNodes/0\" }, { \"$ref\": \"#/allNodes/1\" } ] }" + + " ]" + "}"; + + Graph graph = mapper.readValue(json, Graph.class); + + assertEquals(3, graph.allNodes.size()); + Node n0 = graph.allNodes.get(0); + Node n1 = graph.allNodes.get(1); + Node n2 = graph.allNodes.get(2); + + assertEquals("node0", n0.name); + assertEquals("node1", n1.name); + assertEquals("node2", n2.name); + + assertSame(n0, n1.sibling); + assertEquals(2, n2.neighbors.size()); + assertSame(n0, n2.neighbors.get(0)); + assertSame(n1, n2.neighbors.get(1)); + } + + @Test + public void testCircularStructureSerialization() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + + Node root = new Node("root"); + Node child = new Node("child"); + root.child = child; + child.child = root; // Circular + + Graph graph = new Graph(); + graph.root = root; + graph.allNodes.add(root); + graph.allNodes.add(child); + + try { + mapper.writeValueAsString(graph); + fail(); + } catch (StreamConstraintsException e) { + // this should be thrown by serialization + // so we pass + } + } + + @Test + public void testNestedPathDeserialization() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + + // Test resolving a path that goes through multiple levels of objects and arrays + String json = "{" + " \"root\": {" + " \"neighbors\": [" + + " { \"name\": \"neighbor0\", \"child\": { \"name\": \"inner\" } }" + " ]" + " }," + + " \"allNodes\": [" + " { \"$ref\": \"#/root/neighbors/name/child/name\" }" + " ]" + "}"; + + try { + mapper.readValue(json, Graph.class); + fail(); + } catch (DatabindException e) { + // this should be thrown by deserialization + // so we pass + System.out.println(e); + } + } +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefMapTest.java b/src/test/java/tools/jackson/databind/jref/JRefMapTest.java index 12266f273e..56e879d3ce 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefMapTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefMapTest.java @@ -12,72 +12,72 @@ public class JRefMapTest extends JRefAbstractTest { @Test public void testMapKeyNoRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); String v1 = new String("val1"); String k2 = new String("second"); String v2 = new String("first"); - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); assertJRefCount(out, 0); - trace("testMapKeyNoRef jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapKeyNoRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } @Test public void testMapKeyRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); String v1 = new String("val1"); String k2 = new String("second"); - // second value refs first key, but serialization treats + // second value refs first key, but serialization treats // it like separate string, since map keys cannot be jrefs String v2 = k1; - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); // Because the reference is a key, it should not be serialized to jref // so count is expected to be 0 assertJRefCount(out, 0); - trace("testMapKeyRef jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapKeyRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } @Test public void testMapValueNoRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); String v1 = new String("val1"); String k2 = new String("second"); String v2 = new String("val1"); - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); assertJRefCount(out, 1); - trace("testMapValueNoRef jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapValueNoRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } - + @Test public void testMapValueRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); String v1 = new String("val1"); String k2 = new String("second"); // second value refs first String v2 = v1; - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); assertJRefCount(out, 1); - trace("testMapValueRef jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapValueRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } - + @Test public void testMapValueMultRef() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); String v1 = new String("val1"); String k2 = new String("second"); @@ -88,83 +88,82 @@ public void testMapValueMultRef() throws Exception { String v3 = v1; String k4 = "fourth"; String v4 = v1; - Map mi = Map.of(k1,v1,k2,v2,k3,v3,k4,v4); + Map mi = Map.of(k1, v1, k2, v2, k3, v3, k4, v4); String out = mapper.writeValueAsString(mi); assertJRefCount(out, 3); - trace("testMapValueMultRef jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapValueMultRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } @Test public void testMapKeyNoRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); Object v1 = new String("val1"); String k2 = new String("second"); Object v2 = new String("first"); - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); assertJRefCount(out, 0); - trace("testMapKeyNoRefNoValueType jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapKeyNoRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } @Test public void testMapKeyRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); Object v1 = new String("val1"); String k2 = new String("second"); - // second value refs first key, but serialization treats + // second value refs first key, but serialization treats // it like separate string, since map keys cannot be jrefs Object v2 = k1; - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); // Because the reference is a key, it should not be serialized to jref // so count is expected to be 0 assertJRefCount(out, 0); - trace("testMapKeyRefNoValueType jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapKeyRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } - + @Test public void testMapValueNoRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); Object v1 = new String("val1"); String k2 = new String("second"); Object v2 = new String("val1"); - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); assertJRefCount(out, 1); - trace("testMapValueNoRefNoValueType jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapValueNoRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } - @Test public void testMapValueRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); Object v1 = new String("val1"); String k2 = new String("second"); // second value refs first Object v2 = v1; - Map mi = Map.of(k1,v1,k2,v2); + Map mi = Map.of(k1, v1, k2, v2); String out = mapper.writeValueAsString(mi); assertJRefCount(out, 1); - trace("testMapValueRefNoValueType jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapValueRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } @Test public void testMapValueMultRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); String k1 = new String("first"); Object v1 = new String("val1"); String k2 = new String("second"); @@ -175,11 +174,11 @@ public void testMapValueMultRefNoValueType() throws Exception { Object v3 = v1; String k4 = "fourth"; Object v4 = v1; - Map mi = Map.of(k1,v1,k2,v2,k3,v3,k4,v4); + Map mi = Map.of(k1, v1, k2, v2, k3, v3, k4, v4); String out = mapper.writeValueAsString(mi); assertJRefCount(out, 3); - trace("testMapValueMultRefNoValueType jrefserialized=",out); - Map mo = mapper.readValue(out, Map.class); + trace("testMapValueMultRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); assertEquals(mi, mo); } diff --git a/src/test/java/tools/jackson/databind/jref/JRefNestedTypeTest.java b/src/test/java/tools/jackson/databind/jref/JRefNestedTypeTest.java new file mode 100644 index 0000000000..0217aa41bd --- /dev/null +++ b/src/test/java/tools/jackson/databind/jref/JRefNestedTypeTest.java @@ -0,0 +1,242 @@ +package tools.jackson.databind.jref; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.ObjectMapper; + +public class JRefNestedTypeTest extends JRefAbstractTest { + + record TreeNode(TreeNode parent, String name, String data) { + } + + private static final String ADDRESS = "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\r\n" + + "\r\n" + + "Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\r\n" + + "\r\n" + + "But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth."; + + TreeNode[] buildTwoLevelTopArray() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + // Put top and all nodes in array + return new TreeNode[] { topNode, firstChild, secondChild, thirdChild }; + } + + TreeNode[] buildTwoLevelNoTopArray() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + // Put child nodes in array + return new TreeNode[] { firstChild, secondChild, thirdChild }; + } + + static String JREF_JSON = "[ {\r\n" + " \"parent\" : null,\r\n" + " \"name\" : \"top\",\r\n" + + " \"data\" : \"Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\\r\\n\\r\\nNow we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\\r\\n\\r\\nBut, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth.\"\r\n" + + "}, {\r\n" + " \"parent\" : {\r\n" + " \"$ref\" : \"#/0\"\r\n" + " },\r\n" + + " \"name\" : \"child1\",\r\n" + " \"data\" : \"data1\"\r\n" + "}, {\r\n" + " \"parent\" : {\r\n" + + " \"$ref\" : \"#/0\"\r\n" + " },\r\n" + " \"name\" : \"child2\",\r\n" + " \"data\" : \"data2\"\r\n" + + "}, {\r\n" + " \"parent\" : {\r\n" + " \"$ref\" : \"#/0\"\r\n" + " },\r\n" + + " \"name\" : \"child3\",\r\n" + " \"data\" : \"data3\"\r\n" + "} ]"; + + @Test + void testDeerializeTwoLevelTreeThreeChildrenJRef() { + ObjectMapper mapper = buildObjectMapperJRef(); + TreeNode[] nodes = mapper.readValue(JREF_JSON, TreeNode[].class); + assertEquals(nodes[0], nodes[1].parent); + assertEquals(nodes[0], nodes[2].parent); + assertEquals(nodes[0], nodes[3].parent); + } + + @Test + void testTwoLevelTreeNoJRefNoTop() { + TreeNode[] nodes = buildTwoLevelNoTopArray(); + ObjectMapper mapper = buildObjectMapperNoJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // two jrefs + assertJRefCount(json, 0); + trace("testTwoLevelTreeNoJRefNoTop json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertTrue(out.length == 3); + assertEquals(out[0].parent, out[1].parent); + assertEquals(out[0].parent, out[2].parent); + } + + @Test + void testTwoLevelTreeNoJRefWithTop() { + TreeNode[] nodes = buildTwoLevelTopArray(); + // Object mapper without JRefModule + ObjectMapper mapper = buildObjectMapperNoJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // No jrefs + assertJRefCount(json, 0); + trace("testTwoLevelTreeNoJRef json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertEquals(out.length, 4); + assertEquals(out[0], out[1].parent); + assertEquals(out[0], out[2].parent); + assertEquals(out[0], out[3].parent); + } + + @Test + void testTwoLevelTreeJRefNoTop() { + TreeNode[] nodes = buildTwoLevelNoTopArray(); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // two jrefs + assertJRefCount(json, 2); + trace("testTwoLevelTreeNoJRefNoTop json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertTrue(out.length == 3); + assertEquals(out[0].parent, out[1].parent); + assertEquals(out[0].parent, out[2].parent); + } + + @Test + void testTwoLevelTreeJRefWithTop() { + TreeNode[] nodes = buildTwoLevelTopArray(); + // Object mapper without JRefModule + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // No jrefs + assertJRefCount(json, 3); + trace("testTwoLevelTreeNoJRef json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertEquals(out.length, 4); + assertEquals(out[0], out[1].parent); + assertEquals(out[0], out[2].parent); + assertEquals(out[0], out[3].parent); + } + + TreeNode[] buildThreeLevelArray() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode firstGChild = new TreeNode(firstChild, "gchild1", "data1g"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode secondGChild = new TreeNode(secondChild, "gchild2", "data2g"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + TreeNode thirdGChild = new TreeNode(thirdChild, "child", "data3g"); + + // Put grandchildren nodes in array + return new TreeNode[] { firstGChild, secondGChild, thirdGChild }; + } + + @Test + void testThreeLevelTree() { + TreeNode[] nodes = buildThreeLevelArray(); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // two jrefs + assertJRefCount(json, 2); + trace("testThreeLevelTree json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertTrue(out.length == 3); + assertEquals(out[0].parent.parent, out[1].parent.parent); + assertEquals(out[0].parent.parent, out[2].parent.parent); + } + + List buildThreeLevelArrayAsList() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode firstGChild = new TreeNode(firstChild, "gchild1", "data1g"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode secondGChild = new TreeNode(secondChild, "gchild2", "data2g"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + TreeNode thirdGChild = new TreeNode(thirdChild, "child", "data3g"); + + // Put grandchildren nodes in List + return List.of(firstGChild, secondGChild, thirdGChild); + } + + record TreeNodeList(List nodes) { + } + + @Test + void testThreeLevelTreeNodeListRecord() { + List nodes = buildThreeLevelArrayAsList(); + TreeNodeList tnl = new TreeNodeList(nodes); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tnl); + // two jrefs + assertJRefCount(json, 2); + trace("testThreeLevelTreeNodeListRecord json=", json); + TreeNodeList out = mapper.readValue(json, TreeNodeList.class); + assertTrue(out.nodes().size() == 3); + assertEquals(out.nodes().get(0).parent.parent, out.nodes().get(1).parent.parent); + assertEquals(out.nodes().get(0).parent.parent, out.nodes().get(2).parent.parent); + } + + static class TreeNodeListClass { + + public List nodes; + + } + + @Test + void testThreeLevelTreeNodeListClass() { + TreeNodeListClass tnlc = new TreeNodeListClass(); + tnlc.nodes = buildThreeLevelArrayAsList(); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tnlc); + // two jrefs + assertJRefCount(json, 2); + trace("testThreeLevelTreeNodeListClass json=", json); + TreeNodeListClass out = mapper.readValue(json, TreeNodeListClass.class); + assertTrue(out.nodes.size() == 3); + assertEquals(out.nodes.get(0).parent.parent, out.nodes.get(1).parent.parent); + assertEquals(out.nodes.get(0).parent.parent, out.nodes.get(2).parent.parent); + } + + Map buildThreeLevelArrayAsMap() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode firstGChild = new TreeNode(firstChild, "gchild1", "data1g"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode secondGChild = new TreeNode(secondChild, "gchild2", "data2g"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + TreeNode thirdGChild = new TreeNode(thirdChild, "child", "data3g"); + + // Put grandchildren nodes in Map + return Map.of(firstGChild.name, firstGChild, secondGChild.name, secondGChild, thirdGChild.name, thirdGChild); + } + + record TreeNodeMapRecord(Map nodes) { + } + + @Test + void testThreeLevelTreeNodeMapRecord() { + TreeNodeMapRecord tnmr = new TreeNodeMapRecord(buildThreeLevelArrayAsMap()); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tnmr); + // two jrefs + assertJRefCount(json, 2); + trace("testThreeLevelTreeNodeMapRecord json=", json); + TreeNodeMapRecord out = mapper.readValue(json, TreeNodeMapRecord.class); + assertTrue(out.nodes.size() == 3); + out.nodes().forEach((k, v) -> { + assertEquals(k, v.name); + }); + TreeNode first = null; + for (Map.Entry entry : out.nodes().entrySet()) { + if (first == null) { + first = entry.getValue(); + } else { + assertEquals(first.parent.parent, entry.getValue().parent.parent); + } + } + } + +} diff --git a/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java b/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java index d4ab97d5c9..3535d1d13c 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java @@ -16,13 +16,13 @@ public class JRefRecordBeanTest extends JRefAbstractTest { static record IntBean(Integer j) { } - + @Test public void testIntBeanArray() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); IntBean i1 = new IntBean(10); IntBean i2 = new IntBean(20); - IntBean[] beans = new IntBean[] { i1, i2, i1, i2}; + IntBean[] beans = new IntBean[] { i1, i2, i1, i2 }; String out = mapper.writeValueAsString(beans); trace("testIntBeanArray", out); IntBean[] result = mapper.readValue(out, IntBean[].class); @@ -32,7 +32,7 @@ public void testIntBeanArray() throws Exception { @Test public void testIntBeanList() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); IntBean i1 = new IntBean(10); IntBean i2 = new IntBean(20); List beans = List.of(i1, i2, i1, i2); @@ -44,15 +44,15 @@ public void testIntBeanList() throws Exception { assertEquals(result.get(1), result.get(3)); } - record StringKeyBeanMap(Map items) { + record StringKeyBeanMap(Map items) { } - + @Test public void testStringKeyMap() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); + ObjectMapper mapper = buildObjectMapperJRef(); IntBean i1 = new IntBean(10); IntBean i2 = new IntBean(20); - StringKeyBeanMap beanMap = new StringKeyBeanMap(Map.of("one",i1,"two",i2,"three",i1,"four",i2)); + StringKeyBeanMap beanMap = new StringKeyBeanMap(Map.of("one", i1, "two", i2, "three", i1, "four", i2)); String out = mapper.writeValueAsString(beanMap); trace("testIntBeanStringKeyMap", out); StringKeyBeanMap result = mapper.readValue(out, StringKeyBeanMap.class); @@ -62,18 +62,19 @@ public void testStringKeyMap() throws Exception { record Node(@JsonProperty Node parent, @JsonProperty String name) { } - + record NodeList(@JsonProperty List nodes) { - + } + @Test public void testNodeTree() throws Exception { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - Node root = new Node(null,"root"); - String[] nodeNames = new String[] {"child1","child2","child3"}; + ObjectMapper mapper = buildObjectMapperJRef(); + Node root = new Node(null, "root"); + String[] nodeNames = new String[] { "child1", "child2", "child3" }; List nodeList = new ArrayList<>(); - for(int i =0; i< nodeNames.length; i++) { - nodeList.add(new Node(root,nodeNames[i])); + for (int i = 0; i < nodeNames.length; i++) { + nodeList.add(new Node(root, nodeNames[i])); } // Add references to previously added nodes in reverse order nodeList.add(nodeList.get(2)); @@ -84,17 +85,16 @@ public void testNodeTree() throws Exception { NodeList result = mapper.readValue(out, NodeList.class); List nodes = result.nodes(); // assert nodes list same as input - assertEquals(nodeList.size(),nodes.size()); - for(int firstIndex = 0; firstIndex < nodeList.size() /2; firstIndex++) { + assertEquals(nodeList.size(), nodes.size()); + for (int firstIndex = 0; firstIndex < nodeList.size() / 2; firstIndex++) { int secondIndex = (nodeList.size() - 1) - firstIndex; Node n1 = result.nodes().get(firstIndex); - assertEquals(root.name(),n1.parent().name()); + assertEquals(root.name(), n1.parent().name()); Node n2 = result.nodes().get(secondIndex); - assertEquals(root.name(),n2.parent().name()); - assertEquals(n1,n2); + assertEquals(root.name(), n2.parent().name()); + assertEquals(n1, n2); } } - } diff --git a/src/test/java/tools/jackson/databind/jref/JRefSimpleNestedTypeTest.java b/src/test/java/tools/jackson/databind/jref/JRefSimpleNestedTypeTest.java deleted file mode 100644 index b7766d879f..0000000000 --- a/src/test/java/tools/jackson/databind/jref/JRefSimpleNestedTypeTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package tools.jackson.databind.jref; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import tools.jackson.databind.ObjectMapper; - -public class JRefSimpleNestedTypeTest extends JRefAbstractTest { - - record TreeNode(TreeNode parent, String name, String data) { - } - private static final String ADDRESS = "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\r\n" - + "\r\n" - + "Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\r\n" - + "\r\n" - + "But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth."; - - TreeNode[] buildTwoLevelThreeChildrenArray() { - TreeNode topNode = new TreeNode(null, "top", ADDRESS); - // Create three children - TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); - TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); - TreeNode thirdChild = new TreeNode(topNode, "child3" , "data3"); - // Put top and all nodes in array - return new TreeNode[] { topNode, firstChild, secondChild, thirdChild }; - } - @Test - void testSerializeSingleLevelTreeThreeChildrenNoJRef() { - - ObjectMapper mapper = buildObjectMapperWithoutJRefSupport(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(buildTwoLevelThreeChildrenArray()); - // No jrefs - assertJRefCount(json, 0); - } - - @Test - void testSerializeSingleLevelTreeThreeChildrenJRef() { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(buildTwoLevelThreeChildrenArray()); - assertJRefCount(json, 3); - } - - static String JREF_JSON = "[ {\r\n" - + " \"parent\" : null,\r\n" - + " \"name\" : \"top\",\r\n" - + " \"data\" : \"Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\\r\\n\\r\\nNow we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\\r\\n\\r\\nBut, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth.\"\r\n" - + "}, {\r\n" - + " \"parent\" : {\r\n" - + " \"$ref\" : \"#/0\"\r\n" - + " },\r\n" - + " \"name\" : \"child1\",\r\n" - + " \"data\" : \"data1\"\r\n" - + "}, {\r\n" - + " \"parent\" : {\r\n" - + " \"$ref\" : \"#/0\"\r\n" - + " },\r\n" - + " \"name\" : \"child2\",\r\n" - + " \"data\" : \"data2\"\r\n" - + "}, {\r\n" - + " \"parent\" : {\r\n" - + " \"$ref\" : \"#/0\"\r\n" - + " },\r\n" - + " \"name\" : \"child3\",\r\n" - + " \"data\" : \"data3\"\r\n" - + "} ]"; - @Test - void testDeerializeSingleLevelTreeThreeChildrenJRef() { - ObjectMapper mapper = buildObjectMapperWithJRefSupport(); - TreeNode[] nodes = mapper.readValue(JREF_JSON, TreeNode[].class); - assertEquals(nodes[0],nodes[1].parent); - assertEquals(nodes[0],nodes[2].parent); - assertEquals(nodes[0],nodes[3].parent); - } - -} From 59a713b00ed300ef356343127226d2689f522e6d Mon Sep 17 00:00:00 2001 From: Scott Lewis Date: Fri, 26 Jun 2026 19:22:45 -0700 Subject: [PATCH 3/8] Added value != null and map ! contain value before adding ptr to map. --- src/main/java/tools/jackson/databind/JRefModule.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/tools/jackson/databind/JRefModule.java b/src/main/java/tools/jackson/databind/JRefModule.java index 4a578fda2b..1db55295df 100644 --- a/src/main/java/tools/jackson/databind/JRefModule.java +++ b/src/main/java/tools/jackson/databind/JRefModule.java @@ -103,7 +103,9 @@ void jrefSerialize(Object value, JsonGenerator gen, SerializationContext ctxt, S // serialize the value with delegate serializer.serialize(); // put the object -> ptr into for possible reference usage - valueToPtrMap.put(value, JsonPointer.forPath(gen.streamWriteContext(), false)); + if (value != null && !valueToPtrMap.containsKey(value)) { + valueToPtrMap.put(value, JsonPointer.forPath(gen.streamWriteContext(), false)); + } } } From 8c5aaf21a9ac65886a3123d6514ba4713e62f168 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 17 Jul 2026 17:12:12 -0700 Subject: [PATCH 4/8] Fix minor String formatting bug --- src/main/java/tools/jackson/databind/JRefModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/tools/jackson/databind/JRefModule.java b/src/main/java/tools/jackson/databind/JRefModule.java index 1db55295df..6f3df464a5 100644 --- a/src/main/java/tools/jackson/databind/JRefModule.java +++ b/src/main/java/tools/jackson/databind/JRefModule.java @@ -241,7 +241,7 @@ Object jrefDeserialize(JsonParser p, DeserializationContext ctxt, Deserializer d result = previousResult; } else { throw DatabindException.from(p, - String.format("No previous values present for JsonPointer=%", pathPtr)); + String.format("No previous values present for JsonPointer=%s", pathPtr)); } } catch (IllegalArgumentException e) { throw DatabindException.from(p, String.format("Illegal JsonPointer=%s", path), e); From b008a66d36d45eaaf31fe968c4ae63992b578380 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 17 Jul 2026 17:19:14 -0700 Subject: [PATCH 5/8] Tabs -> spaces for main module --- .../tools/jackson/databind/JRefModule.java | 566 +++++++++--------- 1 file changed, 283 insertions(+), 283 deletions(-) diff --git a/src/main/java/tools/jackson/databind/JRefModule.java b/src/main/java/tools/jackson/databind/JRefModule.java index 6f3df464a5..afcf5231f6 100644 --- a/src/main/java/tools/jackson/databind/JRefModule.java +++ b/src/main/java/tools/jackson/databind/JRefModule.java @@ -53,288 +53,288 @@ **/ public class JRefModule extends SimpleModule { - private static final long serialVersionUID = 1L; - public static final String JREF_NAME = "$ref"; - public static final String HASH = "#"; - - public JRefModule() { - super("JRefModule"); - } - - @Override - public void setupModule(SetupContext context) { - super.setupModule(context); - context.addDeserializerModifier(new JRefValueDeserializerModifier()); - context.addSerializerModifier(new JRefValueSerializerModifier()); - } - - public class JRefValueSerializerModifier extends ValueSerializerModifier { - - private static final long serialVersionUID = 1L; - - static final String PTR_MAP_ATTR = JRefValueSerializerModifier.class.getName() + ".ptrMap"; - - @FunctionalInterface - interface Serializer { - void serialize() throws RuntimeException; - } - - class JRefValueSerializer extends DelegatingSerializer { - - JRefValueSerializer(ValueSerializer delegatee) { - super(delegatee); - } - - void jrefSerialize(Object value, JsonGenerator gen, SerializationContext ctxt, Serializer serializer) { - @SuppressWarnings("unchecked") - Map valueToPtrMap = (Map) ctxt.getAttribute(PTR_MAP_ATTR); - // if it doesn't exist, then create and add as context attribute - if (valueToPtrMap == null) { - valueToPtrMap = new HashMap<>(); - ctxt.setAttribute(PTR_MAP_ATTR, valueToPtrMap); - } - JsonPointer ptr = valueToPtrMap.get(value); - if (ptr != null) { - // If JsonPointer found for value id, write it out and we're done! - gen.writeStartObject(); - gen.writeStringProperty(JREF_NAME, "#" + ptr.toString()); - gen.writeEndObject(); - } else { - // serialize the value with delegate - serializer.serialize(); - // put the object -> ptr into for possible reference usage - if (value != null && !valueToPtrMap.containsKey(value)) { - valueToPtrMap.put(value, JsonPointer.forPath(gen.streamWriteContext(), false)); - } - } - } - - @Override - public void serializeWithType(Object value, JsonGenerator gen, SerializationContext ctxt, - TypeSerializer typeSer) { - jrefSerialize(value, gen, ctxt, () -> super.serializeWithType(value, gen, ctxt, typeSer)); - } - - @Override - public void serialize(Object value, JsonGenerator gen, SerializationContext ctxt) { - jrefSerialize(value, gen, ctxt, () -> super.serialize(value, gen, ctxt)); - } - - @Override - public ValueSerializer newDelegatingInstance(ValueSerializer delegatee) { - return new JRefValueSerializer(delegatee); - } - - } - - @Override - public ValueSerializer modifySerializer(SerializationConfig config, Supplier beanDesc, - ValueSerializer serializer) { - return new JRefValueSerializer(serializer); - } - - @Override - public ValueSerializer modifyArraySerializer(SerializationConfig config, ArrayType valueType, - Supplier beanDesc, ValueSerializer serializer) { - return new JRefValueSerializer(serializer); - } - - @Override - public ValueSerializer modifyCollectionSerializer(SerializationConfig config, CollectionType valueType, - Supplier beanDesc, ValueSerializer serializer) { - return new JRefValueSerializer(serializer); - } - - @Override - public ValueSerializer modifyCollectionLikeSerializer(SerializationConfig config, - CollectionLikeType valueType, Supplier beanDesc, ValueSerializer serializer) { - return new JRefValueSerializer(serializer); - } - - @Override - public ValueSerializer modifyMapSerializer(SerializationConfig config, MapType valueType, Supplier beanDesc, - ValueSerializer serializer) { - return new JRefValueSerializer(serializer); - } - - @Override - public ValueSerializer modifyMapLikeSerializer(SerializationConfig config, MapLikeType valueType, - Supplier beanDesc, ValueSerializer serializer) { - return new JRefValueSerializer(serializer); - } - - @Override - public ValueSerializer modifyEnumSerializer(SerializationConfig config, JavaType valueType, - Supplier beanDesc, ValueSerializer serializer) { - return new JRefValueSerializer(serializer); - } - } - - public class JRefValueDeserializerModifier extends ValueDeserializerModifier { - - private static final long serialVersionUID = 1L; - - static final String STACK_ATTR = JRefValueDeserializerModifier.class.getName() + ".callStack"; - static final String OBJECT_PTR_MAP_ATTR = JRefValueDeserializerModifier.class.getName() + ".objectPtrMap"; - - @FunctionalInterface - interface Deserializer { - Object deserialize(JsonParser p) throws RuntimeException; - } - - class JRefValueDeserializer extends DelegatingDeserializer { - - JRefValueDeserializer(ValueDeserializer src) { - super(src); - } - - Object jrefDeserialize(JsonParser p, DeserializationContext ctxt, Deserializer deserializer) { - @SuppressWarnings("unchecked") - Deque ptrStack = (Deque) ctxt.getAttribute(STACK_ATTR); - if (ptrStack == null) { - // Create on first access - ptrStack = new ArrayDeque<>(); - ctxt.setAttribute(STACK_ATTR, ptrStack); - } - JsonPointer parentPtr = ptrStack.peek(); - if (parentPtr == null) { - // use empty - parentPtr = JsonPointer.empty(); - } - JsonPointer ctxtPtr = JsonPointer.forPath(p.streamReadContext(), false); - // build currPtr from context and parent - JsonPointer currPtr = ctxtPtr.toString().startsWith(parentPtr.toString()) ? ctxtPtr - : parentPtr.append(ctxtPtr); - ptrStack.push(currPtr); - Object result = null; - if (p.currentToken() == JsonToken.START_OBJECT) { - JsonNode node = ctxt.readTree(p); - // Look for "$ref" property - JsonNode jrefValue = node.asObject().get(JREF_NAME); - if (jrefValue != null) { - String pathWithHashExpected = jrefValue.asString(); - // Must start with # (local-only json pointers) - if (!pathWithHashExpected.startsWith(HASH)) { - throw DatabindException.from(p, - String.format("JsonPointer value=%s must start with '#' character (local only)", - pathWithHashExpected)); - } - // Remove hash prefix (local only) - String path = pathWithHashExpected.substring(1); - try { - // create JsonPointer from jref path - JsonPointer pathPtr = JsonPointer.valueOf(path); - if (pathPtr.equals(JsonPointer.empty())) { - throw DatabindException.from(p, "JsonPointer cannot be empty"); - } - @SuppressWarnings("unchecked") - Map resultsMap = (Map) ctxt - .getAttribute(OBJECT_PTR_MAP_ATTR); - if (resultsMap != null) { - // lookup previous result with ptr - Object previousResult = resultsMap.get(pathPtr); - if (previousResult == null) { - throw DatabindException.from(p, - String.format("Could not find previous value for JsonPointer=%s", pathPtr)); - } - // result found - result = previousResult; - } else { - throw DatabindException.from(p, - String.format("No previous values present for JsonPointer=%s", pathPtr)); - } - } catch (IllegalArgumentException e) { - throw DatabindException.from(p, String.format("Illegal JsonPointer=%s", path), e); - } - } - // If we have not found result via jref, then reset parser to - // TreeTraversingParser - if (result == null) { - p = new TreeTraversingParser(node); - if (p.currentToken() != JsonToken.END_OBJECT) { - p.nextToken(); - } - } - } - // call delegate deserializer if no result yet - if (result == null) { - // If jref result not found, delegate serialization by calling super class - result = deserializer.deserialize(p); - if (result != null) { - @SuppressWarnings("unchecked") - Map resultsMap = (Map) ctxt - .getAttribute(OBJECT_PTR_MAP_ATTR); - if (resultsMap == null) { - resultsMap = new HashMap<>(); - ctxt.setAttribute(OBJECT_PTR_MAP_ATTR, resultsMap); - } - resultsMap.put(currPtr, result); - } - } - ptrStack.pollFirst(); - return result; - } - - @Override - public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, - TypeDeserializer typeDeserializer) throws JacksonException { - return jrefDeserialize(p, ctxt, (p1) -> super.deserializeWithType(p1, ctxt, typeDeserializer)); - } - - @Override - public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { - return jrefDeserialize(p, ctxt, (p1) -> super.deserialize(p1, ctxt)); - } - - @Override - protected ValueDeserializer newDelegatingInstance(ValueDeserializer newDelegatee) { - return new JRefValueDeserializer(newDelegatee); - } - - } - - @Override - public ValueDeserializer modifyArrayDeserializer(DeserializationConfig config, ArrayType valueType, - Supplier beanDescRef, ValueDeserializer deserializer) { - return new JRefValueDeserializer(deserializer); - } - - @Override - public ValueDeserializer modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, - Supplier beanDescRef, ValueDeserializer deserializer) { - return new JRefValueDeserializer(deserializer); - } - - @Override - public ValueDeserializer modifyEnumDeserializer(DeserializationConfig config, JavaType type, - Supplier beanDescRef, ValueDeserializer deserializer) { - return new JRefValueDeserializer(deserializer); - } - - @Override - public ValueDeserializer modifyCollectionLikeDeserializer(DeserializationConfig config, - CollectionLikeType type, Supplier beanDescRef, ValueDeserializer deserializer) { - return new JRefValueDeserializer(deserializer); - } - - @Override - public ValueDeserializer modifyDeserializer(DeserializationConfig config, Supplier beanDescRef, - ValueDeserializer deserializer) { - return new JRefValueDeserializer(deserializer); - } - - @Override - public ValueDeserializer modifyMapDeserializer(DeserializationConfig config, MapType type, - Supplier beanDescRef, ValueDeserializer deserializer) { - return new JRefValueDeserializer(deserializer); - } - - @Override - public ValueDeserializer modifyMapLikeDeserializer(DeserializationConfig config, MapLikeType type, - Supplier beanDescRef, ValueDeserializer deserializer) { - return new JRefValueDeserializer(deserializer); - } - - } + private static final long serialVersionUID = 1L; + public static final String JREF_NAME = "$ref"; + public static final String HASH = "#"; + + public JRefModule() { + super("JRefModule"); + } + + @Override + public void setupModule(SetupContext context) { + super.setupModule(context); + context.addDeserializerModifier(new JRefValueDeserializerModifier()); + context.addSerializerModifier(new JRefValueSerializerModifier()); + } + + public class JRefValueSerializerModifier extends ValueSerializerModifier { + + private static final long serialVersionUID = 1L; + + static final String PTR_MAP_ATTR = JRefValueSerializerModifier.class.getName() + ".ptrMap"; + + @FunctionalInterface + interface Serializer { + void serialize() throws RuntimeException; + } + + class JRefValueSerializer extends DelegatingSerializer { + + JRefValueSerializer(ValueSerializer delegatee) { + super(delegatee); + } + + void jrefSerialize(Object value, JsonGenerator gen, SerializationContext ctxt, Serializer serializer) { + @SuppressWarnings("unchecked") + Map valueToPtrMap = (Map) ctxt.getAttribute(PTR_MAP_ATTR); + // if it doesn't exist, then create and add as context attribute + if (valueToPtrMap == null) { + valueToPtrMap = new HashMap<>(); + ctxt.setAttribute(PTR_MAP_ATTR, valueToPtrMap); + } + JsonPointer ptr = valueToPtrMap.get(value); + if (ptr != null) { + // If JsonPointer found for value id, write it out and we're done! + gen.writeStartObject(); + gen.writeStringProperty(JREF_NAME, "#" + ptr.toString()); + gen.writeEndObject(); + } else { + // serialize the value with delegate + serializer.serialize(); + // put the object -> ptr into for possible reference usage + if (value != null && !valueToPtrMap.containsKey(value)) { + valueToPtrMap.put(value, JsonPointer.forPath(gen.streamWriteContext(), false)); + } + } + } + + @Override + public void serializeWithType(Object value, JsonGenerator gen, SerializationContext ctxt, + TypeSerializer typeSer) { + jrefSerialize(value, gen, ctxt, () -> super.serializeWithType(value, gen, ctxt, typeSer)); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctxt) { + jrefSerialize(value, gen, ctxt, () -> super.serialize(value, gen, ctxt)); + } + + @Override + public ValueSerializer newDelegatingInstance(ValueSerializer delegatee) { + return new JRefValueSerializer(delegatee); + } + + } + + @Override + public ValueSerializer modifySerializer(SerializationConfig config, Supplier beanDesc, + ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyArraySerializer(SerializationConfig config, ArrayType valueType, + Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyCollectionSerializer(SerializationConfig config, CollectionType valueType, + Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyCollectionLikeSerializer(SerializationConfig config, + CollectionLikeType valueType, Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyMapSerializer(SerializationConfig config, MapType valueType, Supplier beanDesc, + ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyMapLikeSerializer(SerializationConfig config, MapLikeType valueType, + Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + + @Override + public ValueSerializer modifyEnumSerializer(SerializationConfig config, JavaType valueType, + Supplier beanDesc, ValueSerializer serializer) { + return new JRefValueSerializer(serializer); + } + } + + public class JRefValueDeserializerModifier extends ValueDeserializerModifier { + + private static final long serialVersionUID = 1L; + + static final String STACK_ATTR = JRefValueDeserializerModifier.class.getName() + ".callStack"; + static final String OBJECT_PTR_MAP_ATTR = JRefValueDeserializerModifier.class.getName() + ".objectPtrMap"; + + @FunctionalInterface + interface Deserializer { + Object deserialize(JsonParser p) throws RuntimeException; + } + + class JRefValueDeserializer extends DelegatingDeserializer { + + JRefValueDeserializer(ValueDeserializer src) { + super(src); + } + + Object jrefDeserialize(JsonParser p, DeserializationContext ctxt, Deserializer deserializer) { + @SuppressWarnings("unchecked") + Deque ptrStack = (Deque) ctxt.getAttribute(STACK_ATTR); + if (ptrStack == null) { + // Create on first access + ptrStack = new ArrayDeque<>(); + ctxt.setAttribute(STACK_ATTR, ptrStack); + } + JsonPointer parentPtr = ptrStack.peek(); + if (parentPtr == null) { + // use empty + parentPtr = JsonPointer.empty(); + } + JsonPointer ctxtPtr = JsonPointer.forPath(p.streamReadContext(), false); + // build currPtr from context and parent + JsonPointer currPtr = ctxtPtr.toString().startsWith(parentPtr.toString()) ? ctxtPtr + : parentPtr.append(ctxtPtr); + ptrStack.push(currPtr); + Object result = null; + if (p.currentToken() == JsonToken.START_OBJECT) { + JsonNode node = ctxt.readTree(p); + // Look for "$ref" property + JsonNode jrefValue = node.asObject().get(JREF_NAME); + if (jrefValue != null) { + String pathWithHashExpected = jrefValue.asString(); + // Must start with # (local-only json pointers) + if (!pathWithHashExpected.startsWith(HASH)) { + throw DatabindException.from(p, + String.format("JsonPointer value=%s must start with '#' character (local only)", + pathWithHashExpected)); + } + // Remove hash prefix (local only) + String path = pathWithHashExpected.substring(1); + try { + // create JsonPointer from jref path + JsonPointer pathPtr = JsonPointer.valueOf(path); + if (pathPtr.equals(JsonPointer.empty())) { + throw DatabindException.from(p, "JsonPointer cannot be empty"); + } + @SuppressWarnings("unchecked") + Map resultsMap = (Map) ctxt + .getAttribute(OBJECT_PTR_MAP_ATTR); + if (resultsMap != null) { + // lookup previous result with ptr + Object previousResult = resultsMap.get(pathPtr); + if (previousResult == null) { + throw DatabindException.from(p, + String.format("Could not find previous value for JsonPointer=%s", pathPtr)); + } + // result found + result = previousResult; + } else { + throw DatabindException.from(p, + String.format("No previous values present for JsonPointer=%s", pathPtr)); + } + } catch (IllegalArgumentException e) { + throw DatabindException.from(p, String.format("Illegal JsonPointer=%s", path), e); + } + } + // If we have not found result via jref, then reset parser to + // TreeTraversingParser + if (result == null) { + p = new TreeTraversingParser(node); + if (p.currentToken() != JsonToken.END_OBJECT) { + p.nextToken(); + } + } + } + // call delegate deserializer if no result yet + if (result == null) { + // If jref result not found, delegate serialization by calling super class + result = deserializer.deserialize(p); + if (result != null) { + @SuppressWarnings("unchecked") + Map resultsMap = (Map) ctxt + .getAttribute(OBJECT_PTR_MAP_ATTR); + if (resultsMap == null) { + resultsMap = new HashMap<>(); + ctxt.setAttribute(OBJECT_PTR_MAP_ATTR, resultsMap); + } + resultsMap.put(currPtr, result); + } + } + ptrStack.pollFirst(); + return result; + } + + @Override + public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, + TypeDeserializer typeDeserializer) throws JacksonException { + return jrefDeserialize(p, ctxt, (p1) -> super.deserializeWithType(p1, ctxt, typeDeserializer)); + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException { + return jrefDeserialize(p, ctxt, (p1) -> super.deserialize(p1, ctxt)); + } + + @Override + protected ValueDeserializer newDelegatingInstance(ValueDeserializer newDelegatee) { + return new JRefValueDeserializer(newDelegatee); + } + + } + + @Override + public ValueDeserializer modifyArrayDeserializer(DeserializationConfig config, ArrayType valueType, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyEnumDeserializer(DeserializationConfig config, JavaType type, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyCollectionLikeDeserializer(DeserializationConfig config, + CollectionLikeType type, Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyDeserializer(DeserializationConfig config, Supplier beanDescRef, + ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyMapDeserializer(DeserializationConfig config, MapType type, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + @Override + public ValueDeserializer modifyMapLikeDeserializer(DeserializationConfig config, MapLikeType type, + Supplier beanDescRef, ValueDeserializer deserializer) { + return new JRefValueDeserializer(deserializer); + } + + } } From 73d228b767fa6aace1bb04ba24e64449e5679986 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 17 Jul 2026 17:21:38 -0700 Subject: [PATCH 6/8] tabs->spaces for test classes too --- .../databind/jref/JRefAbstractTest.java | 48 +- .../jackson/databind/jref/JRefArrayTest.java | 362 +++++++------- ...efBeanNonPublicMemberDeserializerTest.java | 466 +++++++++--------- .../JRefBeanPublicMemberDeserializerTest.java | 258 +++++----- .../jref/JRefCircularReferenceTests.java | 212 ++++---- .../jackson/databind/jref/JRefMapTest.java | 342 ++++++------- .../databind/jref/JRefNestedTypeTest.java | 452 ++++++++--------- .../databind/jref/JRefRecordBeanTest.java | 164 +++--- 8 files changed, 1152 insertions(+), 1152 deletions(-) diff --git a/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java b/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java index 75c1ec5ead..83590d735b 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefAbstractTest.java @@ -10,35 +10,35 @@ public class JRefAbstractTest { - public static boolean TRACE = false; + public static boolean TRACE = false; - public static JsonMapper.Builder jsonMapperBuilder() { - return JsonMapper.builder(); - } + public static JsonMapper.Builder jsonMapperBuilder() { + return JsonMapper.builder(); + } - protected ObjectMapper buildObjectMapperJRef() { - return jsonMapperBuilder().addModule(new JRefModule()).build(); - } + protected ObjectMapper buildObjectMapperJRef() { + return jsonMapperBuilder().addModule(new JRefModule()).build(); + } - protected ObjectMapper buildObjectMapperNoJRef() { - return jsonMapperBuilder().build(); - } + protected ObjectMapper buildObjectMapperNoJRef() { + return jsonMapperBuilder().build(); + } - static long countMatches(String text, String target) { - if (text == null || target == null || target.isEmpty()) - return 0; - String quotedTarget = Pattern.quote(target); + static long countMatches(String text, String target) { + if (text == null || target == null || target.isEmpty()) + return 0; + String quotedTarget = Pattern.quote(target); - return Pattern.compile(quotedTarget).matcher(text).results().count(); - } + return Pattern.compile(quotedTarget).matcher(text).results().count(); + } - protected void assertJRefCount(String input, long expectedJRefs) { - assertEquals(expectedJRefs, countMatches(input, "$ref")); - } + protected void assertJRefCount(String input, long expectedJRefs) { + assertEquals(expectedJRefs, countMatches(input, "$ref")); + } - void trace(String method, String s) { - if (TRACE) { - System.out.println(method + "." + s); - } - } + void trace(String method, String s) { + if (TRACE) { + System.out.println(method + "." + s); + } + } } diff --git a/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java b/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java index ca7637841a..6c78b306e2 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefArrayTest.java @@ -12,185 +12,185 @@ public class JRefArrayTest extends JRefAbstractTest { - @Test - void testObjectArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - Object o1 = new Object(); - Object o2 = o1; - Object[] arr = new Object[] { o1, o2 }; - String out = mapper.writeValueAsString(arr); - assertJRefCount(out, 1); - trace("testObjectArrayRef jrefserialized=", out); - Object[] oa = mapper.readValue(out, Object[].class); - assertTrue(oa[0] instanceof Map); - assertTrue(oa[1] instanceof Map); - assertEquals(oa[0], oa[1]); - } - - @Test - void testStringArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String o1 = new String("one"); - String o2 = o1; - String[] arr = new String[] { o1, o2 }; - String out = mapper.writeValueAsString(arr); - assertJRefCount(out, 1); - trace("testStringArrayRef jrefserialized=", out); - String[] oa = mapper.readValue(out, String[].class); - assertTrue(oa[0] instanceof String); - assertTrue(oa[1] instanceof String); - assertEquals(oa[0], oa[1]); - } - - @Test - void test2DObjectArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - Object o1 = new Object(); - Object o2 = o1; - Object[] arr1 = new Object[] { o1, o2 }; - Object[] arr2 = arr1; - String out = mapper.writeValueAsString(new Object[][] { arr1, arr2 }); - assertJRefCount(out, 2); - trace("test2DObjectArrayRef jrefserialized=", out); - Object[][] oa = mapper.readValue(out, Object[][].class); - assertTrue(oa[0][0] instanceof Map); - assertTrue(oa[0][1] instanceof Map); - assertEquals(oa[0], oa[1]); - assertArrayEquals(oa[0], oa[1]); - } - - @Test - void test2DStringArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String o1 = new String("one"); - String o2 = o1; - String[] arr1 = new String[] { o1, o2 }; - String[] arr2 = arr1; - String out = mapper.writeValueAsString(new String[][] { arr1, arr2 }); - assertJRefCount(out, 2); - trace("test2DStringArrayRef jrefserialized=", out); - String[][] oa = mapper.readValue(out, String[][].class); - assertTrue(oa[0][0] instanceof String); - assertTrue(oa[0][1] instanceof String); - assertEquals(oa[0], oa[1]); - assertArrayEquals(oa[0], oa[1]); - } - - @Test - void testIntegerArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - Integer o1 = Integer.valueOf(100); - Integer o2 = o1; - Integer[] arr = new Integer[] { o1, o2 }; - String out = mapper.writeValueAsString(arr); - assertJRefCount(out, 1); - trace("testIntegerArrayRef jrefserialized=", out); - Integer[] oa = mapper.readValue(out, Integer[].class); - assertTrue(oa[0] instanceof Integer); - assertTrue(oa[1] instanceof Integer); - assertEquals(oa[0], oa[1]); - } - - @Test - void test2DIntegerArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - Integer o1 = Integer.valueOf(5); - Integer o2 = o1; - Integer[] arr1 = new Integer[] { o1, o2 }; - Integer[] arr2 = arr1; - String out = mapper.writeValueAsString(new Integer[][] { arr1, arr2 }); - assertJRefCount(out, 2); - trace("test2DStringArrayRef jrefserialized=", out); - Integer[][] oa = mapper.readValue(out, Integer[][].class); - assertTrue(oa[0][0] instanceof Integer); - assertTrue(oa[0][1] instanceof Integer); - assertEquals(oa[0], oa[1]); - assertArrayEquals(oa[0], oa[1]); - } - - @Test - void test3DObjectArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - Object o1 = new Object(); - Object o2 = o1; - Object[] arr1 = new Object[] { o1, o2 }; - Object[][] arr2d = new Object[][] { arr1, arr1 }; - Object[][][] arr3d = new Object[][][] { arr2d, arr2d }; - - String out = mapper.writeValueAsString(arr3d); - assertJRefCount(out, 3); - trace("test3DObjectArrayRef jrefserialized=", out); - - Object[][][] oa = mapper.readValue(out, Object[][][].class); - assertEquals(oa[0], oa[1]); - assertEquals(oa[0][0], oa[0][1]); - assertEquals(oa[0][0][0], oa[0][0][1]); - assertTrue(oa[0][0][0] instanceof Map); - } - - @Test - void test3DStringArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String s1 = new String("test"); - String[] arr1 = new String[] { s1, s1 }; - String[][] arr2d = new String[][] { arr1, arr1 }; - String[][][] arr3d = new String[][][] { arr2d, arr2d }; - - String out = mapper.writeValueAsString(arr3d); - assertJRefCount(out, 3); - trace("test3DStringArrayRef jrefserialized=", out); - - String[][][] oa = mapper.readValue(out, String[][][].class); - assertArrayEquals(oa[0], oa[1]); - assertArrayEquals(oa[0][0], oa[0][1]); - assertEquals(oa[0][0][0], oa[0][0][1]); - assertTrue(oa[0][0][0] instanceof String); - } - - @Test - void test4DObjectArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - Object o1 = new Object(); - Object[] arr1 = new Object[] { o1 }; - Object[][] arr2 = new Object[][] { arr1 }; - Object[][][] arr3 = new Object[][][] { arr2 }; - Object[][][][] arr4 = new Object[][][][] { arr3, arr3 }; - - String out = mapper.writeValueAsString(arr4); - assertJRefCount(out, 1); - trace("test4DObjectArrayRef jrefserialized=", out); - - Object[][][][] oa = mapper.readValue(out, Object[][][][].class); - assertEquals(oa[0], oa[1]); - assertEquals(oa[0][0], oa[1][0]); - assertEquals(oa[0][0][0], oa[1][0][0]); - assertTrue(oa[0][0][0][0] instanceof Map); - } - - @Test - void test4DStringArrayRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String s1 = new String("deep"); - String s2 = new String("thoughts"); - String[] arr1 = new String[] { s1, s2, s2 }; - String[] arr1a = arr1; - String[][] arr2 = new String[][] { arr1, arr1a }; - String[][] arr2a = arr2; - String[][][] arr3 = new String[][][] { arr2, arr2a }; - String[][][] arr3a = arr3; - String[][][][] arr4 = new String[][][][] { arr3, arr3a, arr3 }; - - String out = mapper.writeValueAsString(arr4); - assertJRefCount(out, 5); - trace("test4DStringArrayRef jrefserialized=", out); - - String[][][][] oa = mapper.readValue(out, String[][][][].class); - assertArrayEquals(oa[0], oa[1]); - assertArrayEquals(oa[0], oa[2]); - assertArrayEquals(oa[0][0], oa[0][1]); - assertArrayEquals(oa[0][0][0], oa[0][0][1]); - assertEquals(oa[0][0][0][0], "deep"); - assertEquals(oa[0][0][0][1], "thoughts"); - - } + @Test + void testObjectArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + Object o1 = new Object(); + Object o2 = o1; + Object[] arr = new Object[] { o1, o2 }; + String out = mapper.writeValueAsString(arr); + assertJRefCount(out, 1); + trace("testObjectArrayRef jrefserialized=", out); + Object[] oa = mapper.readValue(out, Object[].class); + assertTrue(oa[0] instanceof Map); + assertTrue(oa[1] instanceof Map); + assertEquals(oa[0], oa[1]); + } + + @Test + void testStringArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String o1 = new String("one"); + String o2 = o1; + String[] arr = new String[] { o1, o2 }; + String out = mapper.writeValueAsString(arr); + assertJRefCount(out, 1); + trace("testStringArrayRef jrefserialized=", out); + String[] oa = mapper.readValue(out, String[].class); + assertTrue(oa[0] instanceof String); + assertTrue(oa[1] instanceof String); + assertEquals(oa[0], oa[1]); + } + + @Test + void test2DObjectArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + Object o1 = new Object(); + Object o2 = o1; + Object[] arr1 = new Object[] { o1, o2 }; + Object[] arr2 = arr1; + String out = mapper.writeValueAsString(new Object[][] { arr1, arr2 }); + assertJRefCount(out, 2); + trace("test2DObjectArrayRef jrefserialized=", out); + Object[][] oa = mapper.readValue(out, Object[][].class); + assertTrue(oa[0][0] instanceof Map); + assertTrue(oa[0][1] instanceof Map); + assertEquals(oa[0], oa[1]); + assertArrayEquals(oa[0], oa[1]); + } + + @Test + void test2DStringArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String o1 = new String("one"); + String o2 = o1; + String[] arr1 = new String[] { o1, o2 }; + String[] arr2 = arr1; + String out = mapper.writeValueAsString(new String[][] { arr1, arr2 }); + assertJRefCount(out, 2); + trace("test2DStringArrayRef jrefserialized=", out); + String[][] oa = mapper.readValue(out, String[][].class); + assertTrue(oa[0][0] instanceof String); + assertTrue(oa[0][1] instanceof String); + assertEquals(oa[0], oa[1]); + assertArrayEquals(oa[0], oa[1]); + } + + @Test + void testIntegerArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + Integer o1 = Integer.valueOf(100); + Integer o2 = o1; + Integer[] arr = new Integer[] { o1, o2 }; + String out = mapper.writeValueAsString(arr); + assertJRefCount(out, 1); + trace("testIntegerArrayRef jrefserialized=", out); + Integer[] oa = mapper.readValue(out, Integer[].class); + assertTrue(oa[0] instanceof Integer); + assertTrue(oa[1] instanceof Integer); + assertEquals(oa[0], oa[1]); + } + + @Test + void test2DIntegerArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + Integer o1 = Integer.valueOf(5); + Integer o2 = o1; + Integer[] arr1 = new Integer[] { o1, o2 }; + Integer[] arr2 = arr1; + String out = mapper.writeValueAsString(new Integer[][] { arr1, arr2 }); + assertJRefCount(out, 2); + trace("test2DStringArrayRef jrefserialized=", out); + Integer[][] oa = mapper.readValue(out, Integer[][].class); + assertTrue(oa[0][0] instanceof Integer); + assertTrue(oa[0][1] instanceof Integer); + assertEquals(oa[0], oa[1]); + assertArrayEquals(oa[0], oa[1]); + } + + @Test + void test3DObjectArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + Object o1 = new Object(); + Object o2 = o1; + Object[] arr1 = new Object[] { o1, o2 }; + Object[][] arr2d = new Object[][] { arr1, arr1 }; + Object[][][] arr3d = new Object[][][] { arr2d, arr2d }; + + String out = mapper.writeValueAsString(arr3d); + assertJRefCount(out, 3); + trace("test3DObjectArrayRef jrefserialized=", out); + + Object[][][] oa = mapper.readValue(out, Object[][][].class); + assertEquals(oa[0], oa[1]); + assertEquals(oa[0][0], oa[0][1]); + assertEquals(oa[0][0][0], oa[0][0][1]); + assertTrue(oa[0][0][0] instanceof Map); + } + + @Test + void test3DStringArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String s1 = new String("test"); + String[] arr1 = new String[] { s1, s1 }; + String[][] arr2d = new String[][] { arr1, arr1 }; + String[][][] arr3d = new String[][][] { arr2d, arr2d }; + + String out = mapper.writeValueAsString(arr3d); + assertJRefCount(out, 3); + trace("test3DStringArrayRef jrefserialized=", out); + + String[][][] oa = mapper.readValue(out, String[][][].class); + assertArrayEquals(oa[0], oa[1]); + assertArrayEquals(oa[0][0], oa[0][1]); + assertEquals(oa[0][0][0], oa[0][0][1]); + assertTrue(oa[0][0][0] instanceof String); + } + + @Test + void test4DObjectArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + Object o1 = new Object(); + Object[] arr1 = new Object[] { o1 }; + Object[][] arr2 = new Object[][] { arr1 }; + Object[][][] arr3 = new Object[][][] { arr2 }; + Object[][][][] arr4 = new Object[][][][] { arr3, arr3 }; + + String out = mapper.writeValueAsString(arr4); + assertJRefCount(out, 1); + trace("test4DObjectArrayRef jrefserialized=", out); + + Object[][][][] oa = mapper.readValue(out, Object[][][][].class); + assertEquals(oa[0], oa[1]); + assertEquals(oa[0][0], oa[1][0]); + assertEquals(oa[0][0][0], oa[1][0][0]); + assertTrue(oa[0][0][0][0] instanceof Map); + } + + @Test + void test4DStringArrayRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String s1 = new String("deep"); + String s2 = new String("thoughts"); + String[] arr1 = new String[] { s1, s2, s2 }; + String[] arr1a = arr1; + String[][] arr2 = new String[][] { arr1, arr1a }; + String[][] arr2a = arr2; + String[][][] arr3 = new String[][][] { arr2, arr2a }; + String[][][] arr3a = arr3; + String[][][][] arr4 = new String[][][][] { arr3, arr3a, arr3 }; + + String out = mapper.writeValueAsString(arr4); + assertJRefCount(out, 5); + trace("test4DStringArrayRef jrefserialized=", out); + + String[][][][] oa = mapper.readValue(out, String[][][][].class); + assertArrayEquals(oa[0], oa[1]); + assertArrayEquals(oa[0], oa[2]); + assertArrayEquals(oa[0][0], oa[0][1]); + assertArrayEquals(oa[0][0][0], oa[0][0][1]); + assertEquals(oa[0][0][0][0], "deep"); + assertEquals(oa[0][0][0][1], "thoughts"); + + } } diff --git a/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java b/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java index 9b35f2079c..83c817d068 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefBeanNonPublicMemberDeserializerTest.java @@ -14,238 +14,238 @@ public class JRefBeanNonPublicMemberDeserializerTest extends JRefAbstractTest { - @Test - public void testMapRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - Object v1 = new String("val1"); - String k2 = new String("second"); - // second value refs first - Object v2 = v1; - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - trace("testMapRefNoValueType jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapNoRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - Object v1 = new String("val1"); - String k2 = new String("second"); - // second value refs first - Object v2 = new String("val1"); - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - trace("testMapNoRefNoValueType jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testStringListTwoValue() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - // These are two separate instances, with same string underneath - String s1 = new String("one"); - String s2 = new String("one"); - List sl = List.of(s1, s2); - String out = mapper.writeValueAsString(sl); - trace("testStringListTwoValue jrefserialized=", out); - List result = mapper.readValue(out, List.class); - assertEquals(result.get(0), s1); - assertEquals(result.get(1), s2); - } - - @Test - public void testStringListOneValue() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String s1 = new String("one"); - // s2 is reference to s1 - String s2 = s1; - List sl = List.of(s1, s2); - String out = mapper.writeValueAsString(sl); - trace("testStringListOneValue jrefserialized=", out); - List result = mapper.readValue(out, List.class); - assertEquals(result.get(0), s1); - assertEquals(result.get(1), s2); - } - - @Test - public void testIntList() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - List l = List.of(10, 10); - String input = mapper.writeValueAsString(l); - trace("testIntegerList", input); - List result = mapper.readValue(input, List.class); - assertEquals(l, result); - - } - - static class IntType { - @JsonProperty - int i; - } - - static class IntItems { - @JsonProperty - int j; - @JsonProperty - List items; - } - - @Test - public void testIntItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"j\": 20, \"items\":[ { \"i\": 10}, { \"i\": { \"$ref\": \"#/items/0/i\" }}, { \"i\": { \"$ref\": \"#/j\" }}]}"; - IntItems result = mapper.readValue(input, IntItems.class); - assertEquals(result.items.get(0).i, result.items.get(1).i); - assertEquals(result.j, result.items.get(2).i); - } - - static class StringItems { - @JsonProperty - List items; - @JsonProperty - String second; - } - - @Test - public void testStringItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[\"hello\", { \"$ref\": \"#/items/0\" }], \"second\": { \"$ref\": \"#/items/0\" }}"; - StringItems result = mapper.readValue(input, StringItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - assertEquals(result.items.get(0), result.second); - } - - static class IntegerItems { - @JsonProperty - List items; - } - - @Test - public void testIntegerItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[5, { \"$ref\": \"#/items/0\" }]}"; - IntegerItems result = mapper.readValue(input, IntegerItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - } - - static class DoubleItems { - @JsonProperty - List items; - } - - @Test - public void testDoubleItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; - DoubleItems result = mapper.readValue(input, DoubleItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - } - - static class FloatItems { - @JsonProperty - List items; - } - - @Test - public void testFloatItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; - FloatItems result = mapper.readValue(input, FloatItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - } - - static class BooleanItems { - @JsonProperty - List items; - } - - @Test - public void testBooleanItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[true, { \"$ref\": \"#/items/0\" }]}"; - BooleanItems result = mapper.readValue(input, BooleanItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - } - - @JsonInclude(JsonInclude.Include.NON_NULL) - static class Human { - @JsonProperty - String name; - @JsonProperty - Human parent; - @JsonProperty - Map props; - @JsonProperty - Human o; - @JsonProperty - String otherName; - @JsonProperty - Map moreProps; - - public Human() { - } - - @Override - public String toString() { - return "Human[name=" + name + ", parent=" + parent + ", props=" + props + ", o=" + this.o + "]"; - } - - } - - static class Message { - @JsonProperty - List items; - - public Message() { - - } - - public Message(List items) { - this.items = items; - } - - @Override - public String toString() { - return "Message[items=" + items + "]"; - } - } - - @Test - public void testStringItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - // Input has first item in Message.items list fully defined, and second item - // jrefs to first item - String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 }, \"otherName\": { \"$ref\": \"#/items/0/name\" } }]}"; - - Message msg = mapper.readValue(message, Message.class); - assertEquals(msg.items.get(0).name, msg.items.get(0).otherName); - } - - @Test - public void testCollectionStringKeyItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - // Input has first item in Message.items list fully defined, and second item - // jrefs to first item - String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"$ref\": \"#/items/0\" }]}"; - - Message msg = mapper.readValue(message, Message.class); - assertEquals(msg.items.get(0), msg.items.get(1)); - } - - @Test - public void testCollectionObjectKeyItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - // Input has first item in Message.items list fully defined, and second item - // jrefs to first item - String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"name\": \"wendy\", \"parent\": null, \"moreProps\": { \"$ref\": \"#/items/0/props\" }}]}"; - - Message msg = mapper.readValue(message, Message.class); - assertEquals(msg.items.get(0).props, msg.items.get(1).moreProps); - } + @Test + public void testMapRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + Object v2 = v1; + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + trace("testMapRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapNoRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + Object v2 = new String("val1"); + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + trace("testMapNoRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testStringListTwoValue() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + // These are two separate instances, with same string underneath + String s1 = new String("one"); + String s2 = new String("one"); + List sl = List.of(s1, s2); + String out = mapper.writeValueAsString(sl); + trace("testStringListTwoValue jrefserialized=", out); + List result = mapper.readValue(out, List.class); + assertEquals(result.get(0), s1); + assertEquals(result.get(1), s2); + } + + @Test + public void testStringListOneValue() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String s1 = new String("one"); + // s2 is reference to s1 + String s2 = s1; + List sl = List.of(s1, s2); + String out = mapper.writeValueAsString(sl); + trace("testStringListOneValue jrefserialized=", out); + List result = mapper.readValue(out, List.class); + assertEquals(result.get(0), s1); + assertEquals(result.get(1), s2); + } + + @Test + public void testIntList() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + List l = List.of(10, 10); + String input = mapper.writeValueAsString(l); + trace("testIntegerList", input); + List result = mapper.readValue(input, List.class); + assertEquals(l, result); + + } + + static class IntType { + @JsonProperty + int i; + } + + static class IntItems { + @JsonProperty + int j; + @JsonProperty + List items; + } + + @Test + public void testIntItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"j\": 20, \"items\":[ { \"i\": 10}, { \"i\": { \"$ref\": \"#/items/0/i\" }}, { \"i\": { \"$ref\": \"#/j\" }}]}"; + IntItems result = mapper.readValue(input, IntItems.class); + assertEquals(result.items.get(0).i, result.items.get(1).i); + assertEquals(result.j, result.items.get(2).i); + } + + static class StringItems { + @JsonProperty + List items; + @JsonProperty + String second; + } + + @Test + public void testStringItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[\"hello\", { \"$ref\": \"#/items/0\" }], \"second\": { \"$ref\": \"#/items/0\" }}"; + StringItems result = mapper.readValue(input, StringItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + assertEquals(result.items.get(0), result.second); + } + + static class IntegerItems { + @JsonProperty + List items; + } + + @Test + public void testIntegerItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[5, { \"$ref\": \"#/items/0\" }]}"; + IntegerItems result = mapper.readValue(input, IntegerItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class DoubleItems { + @JsonProperty + List items; + } + + @Test + public void testDoubleItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; + DoubleItems result = mapper.readValue(input, DoubleItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class FloatItems { + @JsonProperty + List items; + } + + @Test + public void testFloatItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; + FloatItems result = mapper.readValue(input, FloatItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class BooleanItems { + @JsonProperty + List items; + } + + @Test + public void testBooleanItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[true, { \"$ref\": \"#/items/0\" }]}"; + BooleanItems result = mapper.readValue(input, BooleanItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + static class Human { + @JsonProperty + String name; + @JsonProperty + Human parent; + @JsonProperty + Map props; + @JsonProperty + Human o; + @JsonProperty + String otherName; + @JsonProperty + Map moreProps; + + public Human() { + } + + @Override + public String toString() { + return "Human[name=" + name + ", parent=" + parent + ", props=" + props + ", o=" + this.o + "]"; + } + + } + + static class Message { + @JsonProperty + List items; + + public Message() { + + } + + public Message(List items) { + this.items = items; + } + + @Override + public String toString() { + return "Message[items=" + items + "]"; + } + } + + @Test + public void testStringItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 }, \"otherName\": { \"$ref\": \"#/items/0/name\" } }]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0).name, msg.items.get(0).otherName); + } + + @Test + public void testCollectionStringKeyItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"$ref\": \"#/items/0\" }]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0), msg.items.get(1)); + } + + @Test + public void testCollectionObjectKeyItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"name\": \"wendy\", \"parent\": null, \"moreProps\": { \"$ref\": \"#/items/0/props\" }}]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0).props, msg.items.get(1).moreProps); + } } diff --git a/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberDeserializerTest.java b/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberDeserializerTest.java index e772bea104..7020303115 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberDeserializerTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefBeanPublicMemberDeserializerTest.java @@ -11,134 +11,134 @@ public class JRefBeanPublicMemberDeserializerTest extends JRefAbstractTest { - static class StringItems { - public List items; - public String second; - } - - @Test - public void testStringItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[\"hello\", { \"$ref\": \"#/items/0\" }], \"second\": { \"$ref\": \"#/items/0\" }}"; - StringItems result = mapper.readValue(input, StringItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - assertEquals(result.items.get(0), result.second); - } - - static class IntegerItems { - public List items; - } - - @Test - public void testIntegerItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[5, { \"$ref\": \"#/items/0\" }]}"; - IntegerItems result = mapper.readValue(input, IntegerItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - } - - static class DoubleItems { - public List items; - } - - @Test - public void testDoubleItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; - DoubleItems result = mapper.readValue(input, DoubleItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - } - - static class FloatItems { - public List items; - } - - @Test - public void testFloatItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; - FloatItems result = mapper.readValue(input, FloatItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - } - - static class BooleanItems { - public List items; - } - - @Test - public void testBooleanItems() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String input = "{\"items\":[true, { \"$ref\": \"#/items/0\" }]}"; - BooleanItems result = mapper.readValue(input, BooleanItems.class); - assertEquals(result.items.get(0), result.items.get(1)); - } - - static class Human { - public String name; - public Human parent; - public Map props; - public Human o; - public String otherName; - public Map moreProps; - - public Human() { - } - - @Override - public String toString() { - return "Human[name=" + name + ", parent=" + parent + ", props=" + props + ", o=" + this.o + "]"; - } - - } - - static class Message { - public List items; - - public Message() { - - } - - public Message(List items) { - this.items = items; - } - - @Override - public String toString() { - return "Message[items=" + items + "]"; - } - } - - @Test - public void testStringItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - // Input has first item in Message.items list fully defined, and second item - // jrefs to first item - String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 }, \"otherName\": { \"$ref\": \"#/items/0/name\" } }]}"; - - Message msg = mapper.readValue(message, Message.class); - assertEquals(msg.items.get(0).name, msg.items.get(0).otherName); - } - - @Test - public void testCollectionStringKeyItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - // Input has first item in Message.items list fully defined, and second item - // jrefs to first item - String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"$ref\": \"#/items/0\" }]}"; - - Message msg = mapper.readValue(message, Message.class); - assertEquals(msg.items.get(0), msg.items.get(1)); - } - - @Test - public void testCollectionObjectKeyItemPath() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - // Input has first item in Message.items list fully defined, and second item - // jrefs to first item - String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"name\": \"wendy\", \"parent\": null, \"moreProps\": { \"$ref\": \"#/items/0/props\" }}]}"; - - Message msg = mapper.readValue(message, Message.class); - assertEquals(msg.items.get(0).props, msg.items.get(1).moreProps); - } + static class StringItems { + public List items; + public String second; + } + + @Test + public void testStringItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[\"hello\", { \"$ref\": \"#/items/0\" }], \"second\": { \"$ref\": \"#/items/0\" }}"; + StringItems result = mapper.readValue(input, StringItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + assertEquals(result.items.get(0), result.second); + } + + static class IntegerItems { + public List items; + } + + @Test + public void testIntegerItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[5, { \"$ref\": \"#/items/0\" }]}"; + IntegerItems result = mapper.readValue(input, IntegerItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class DoubleItems { + public List items; + } + + @Test + public void testDoubleItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; + DoubleItems result = mapper.readValue(input, DoubleItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class FloatItems { + public List items; + } + + @Test + public void testFloatItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[5.0, { \"$ref\": \"#/items/0\" }]}"; + FloatItems result = mapper.readValue(input, FloatItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class BooleanItems { + public List items; + } + + @Test + public void testBooleanItems() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String input = "{\"items\":[true, { \"$ref\": \"#/items/0\" }]}"; + BooleanItems result = mapper.readValue(input, BooleanItems.class); + assertEquals(result.items.get(0), result.items.get(1)); + } + + static class Human { + public String name; + public Human parent; + public Map props; + public Human o; + public String otherName; + public Map moreProps; + + public Human() { + } + + @Override + public String toString() { + return "Human[name=" + name + ", parent=" + parent + ", props=" + props + ", o=" + this.o + "]"; + } + + } + + static class Message { + public List items; + + public Message() { + + } + + public Message(List items) { + this.items = items; + } + + @Override + public String toString() { + return "Message[items=" + items + "]"; + } + } + + @Test + public void testStringItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 }, \"otherName\": { \"$ref\": \"#/items/0/name\" } }]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0).name, msg.items.get(0).otherName); + } + + @Test + public void testCollectionStringKeyItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"$ref\": \"#/items/0\" }]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0), msg.items.get(1)); + } + + @Test + public void testCollectionObjectKeyItemPath() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + // Input has first item in Message.items list fully defined, and second item + // jrefs to first item + String message = "{\"items\": [{ \"name\": \"sam\", \"parent\": null, \"props\": { \"p\": 1 } }, { \"name\": \"wendy\", \"parent\": null, \"moreProps\": { \"$ref\": \"#/items/0/props\" }}]}"; + + Message msg = mapper.readValue(message, Message.class); + assertEquals(msg.items.get(0).props, msg.items.get(1).moreProps); + } } diff --git a/src/test/java/tools/jackson/databind/jref/JRefCircularReferenceTests.java b/src/test/java/tools/jackson/databind/jref/JRefCircularReferenceTests.java index c288ded311..5961749abf 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefCircularReferenceTests.java +++ b/src/test/java/tools/jackson/databind/jref/JRefCircularReferenceTests.java @@ -15,110 +15,110 @@ public class JRefCircularReferenceTests extends JRefAbstractTest { - static class Node { - public String name; - public Node child; - public Node sibling; - public List neighbors = new ArrayList<>(); - - public Node() { - } - - public Node(String name) { - this.name = name; - } - } - - static class Graph { - public Node root; - public List allNodes = new ArrayList<>(); - } - - @Test - public void testNestedCircularDeserialization() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - - // JSON representing a circular structure: root -> child -> child (ref to root) - String json = "{" + " \"root\": {" + " \"name\": \"parent\"," + " \"child\": {" - + " \"name\": \"child\"," + " \"child\": { \"$ref\": \"#/root\" }" + " }" + " }" + "}"; - - try { - mapper.readValue(json, Graph.class); - fail(); - } catch (DatabindException e) { - // this should be thrown by deserialization - // so we pass - } - - } - - @Test - public void testSiblingAndNeighborDeserialization() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - - // JSON representing nodes where neighbors refer back to previous nodes in a - // list - String json = "{" + " \"allNodes\": [" + " { \"name\": \"node0\" }," - + " { \"name\": \"node1\", \"sibling\": { \"$ref\": \"#/allNodes/0\" } }," - + " { \"name\": \"node2\", \"neighbors\": [ { \"$ref\": \"#/allNodes/0\" }, { \"$ref\": \"#/allNodes/1\" } ] }" - + " ]" + "}"; - - Graph graph = mapper.readValue(json, Graph.class); - - assertEquals(3, graph.allNodes.size()); - Node n0 = graph.allNodes.get(0); - Node n1 = graph.allNodes.get(1); - Node n2 = graph.allNodes.get(2); - - assertEquals("node0", n0.name); - assertEquals("node1", n1.name); - assertEquals("node2", n2.name); - - assertSame(n0, n1.sibling); - assertEquals(2, n2.neighbors.size()); - assertSame(n0, n2.neighbors.get(0)); - assertSame(n1, n2.neighbors.get(1)); - } - - @Test - public void testCircularStructureSerialization() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - - Node root = new Node("root"); - Node child = new Node("child"); - root.child = child; - child.child = root; // Circular - - Graph graph = new Graph(); - graph.root = root; - graph.allNodes.add(root); - graph.allNodes.add(child); - - try { - mapper.writeValueAsString(graph); - fail(); - } catch (StreamConstraintsException e) { - // this should be thrown by serialization - // so we pass - } - } - - @Test - public void testNestedPathDeserialization() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - - // Test resolving a path that goes through multiple levels of objects and arrays - String json = "{" + " \"root\": {" + " \"neighbors\": [" - + " { \"name\": \"neighbor0\", \"child\": { \"name\": \"inner\" } }" + " ]" + " }," - + " \"allNodes\": [" + " { \"$ref\": \"#/root/neighbors/name/child/name\" }" + " ]" + "}"; - - try { - mapper.readValue(json, Graph.class); - fail(); - } catch (DatabindException e) { - // this should be thrown by deserialization - // so we pass - System.out.println(e); - } - } + static class Node { + public String name; + public Node child; + public Node sibling; + public List neighbors = new ArrayList<>(); + + public Node() { + } + + public Node(String name) { + this.name = name; + } + } + + static class Graph { + public Node root; + public List allNodes = new ArrayList<>(); + } + + @Test + public void testNestedCircularDeserialization() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + + // JSON representing a circular structure: root -> child -> child (ref to root) + String json = "{" + " \"root\": {" + " \"name\": \"parent\"," + " \"child\": {" + + " \"name\": \"child\"," + " \"child\": { \"$ref\": \"#/root\" }" + " }" + " }" + "}"; + + try { + mapper.readValue(json, Graph.class); + fail(); + } catch (DatabindException e) { + // this should be thrown by deserialization + // so we pass + } + + } + + @Test + public void testSiblingAndNeighborDeserialization() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + + // JSON representing nodes where neighbors refer back to previous nodes in a + // list + String json = "{" + " \"allNodes\": [" + " { \"name\": \"node0\" }," + + " { \"name\": \"node1\", \"sibling\": { \"$ref\": \"#/allNodes/0\" } }," + + " { \"name\": \"node2\", \"neighbors\": [ { \"$ref\": \"#/allNodes/0\" }, { \"$ref\": \"#/allNodes/1\" } ] }" + + " ]" + "}"; + + Graph graph = mapper.readValue(json, Graph.class); + + assertEquals(3, graph.allNodes.size()); + Node n0 = graph.allNodes.get(0); + Node n1 = graph.allNodes.get(1); + Node n2 = graph.allNodes.get(2); + + assertEquals("node0", n0.name); + assertEquals("node1", n1.name); + assertEquals("node2", n2.name); + + assertSame(n0, n1.sibling); + assertEquals(2, n2.neighbors.size()); + assertSame(n0, n2.neighbors.get(0)); + assertSame(n1, n2.neighbors.get(1)); + } + + @Test + public void testCircularStructureSerialization() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + + Node root = new Node("root"); + Node child = new Node("child"); + root.child = child; + child.child = root; // Circular + + Graph graph = new Graph(); + graph.root = root; + graph.allNodes.add(root); + graph.allNodes.add(child); + + try { + mapper.writeValueAsString(graph); + fail(); + } catch (StreamConstraintsException e) { + // this should be thrown by serialization + // so we pass + } + } + + @Test + public void testNestedPathDeserialization() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + + // Test resolving a path that goes through multiple levels of objects and arrays + String json = "{" + " \"root\": {" + " \"neighbors\": [" + + " { \"name\": \"neighbor0\", \"child\": { \"name\": \"inner\" } }" + " ]" + " }," + + " \"allNodes\": [" + " { \"$ref\": \"#/root/neighbors/name/child/name\" }" + " ]" + "}"; + + try { + mapper.readValue(json, Graph.class); + fail(); + } catch (DatabindException e) { + // this should be thrown by deserialization + // so we pass + System.out.println(e); + } + } } diff --git a/src/test/java/tools/jackson/databind/jref/JRefMapTest.java b/src/test/java/tools/jackson/databind/jref/JRefMapTest.java index 56e879d3ce..ea590e0ebf 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefMapTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefMapTest.java @@ -10,176 +10,176 @@ public class JRefMapTest extends JRefAbstractTest { - @Test - public void testMapKeyNoRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - String v1 = new String("val1"); - String k2 = new String("second"); - String v2 = new String("first"); - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - assertJRefCount(out, 0); - trace("testMapKeyNoRef jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapKeyRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - String v1 = new String("val1"); - String k2 = new String("second"); - // second value refs first key, but serialization treats - // it like separate string, since map keys cannot be jrefs - String v2 = k1; - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - // Because the reference is a key, it should not be serialized to jref - // so count is expected to be 0 - assertJRefCount(out, 0); - trace("testMapKeyRef jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapValueNoRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - String v1 = new String("val1"); - String k2 = new String("second"); - String v2 = new String("val1"); - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - assertJRefCount(out, 1); - trace("testMapValueNoRef jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapValueRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - String v1 = new String("val1"); - String k2 = new String("second"); - // second value refs first - String v2 = v1; - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - assertJRefCount(out, 1); - trace("testMapValueRef jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapValueMultRef() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - String v1 = new String("val1"); - String k2 = new String("second"); - // second value refs first - String v2 = v1; - String k3 = "third"; - // third value refs first - String v3 = v1; - String k4 = "fourth"; - String v4 = v1; - Map mi = Map.of(k1, v1, k2, v2, k3, v3, k4, v4); - String out = mapper.writeValueAsString(mi); - assertJRefCount(out, 3); - trace("testMapValueMultRef jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapKeyNoRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - Object v1 = new String("val1"); - String k2 = new String("second"); - Object v2 = new String("first"); - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - assertJRefCount(out, 0); - trace("testMapKeyNoRefNoValueType jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapKeyRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - Object v1 = new String("val1"); - String k2 = new String("second"); - // second value refs first key, but serialization treats - // it like separate string, since map keys cannot be jrefs - Object v2 = k1; - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - // Because the reference is a key, it should not be serialized to jref - // so count is expected to be 0 - assertJRefCount(out, 0); - trace("testMapKeyRefNoValueType jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapValueNoRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - Object v1 = new String("val1"); - String k2 = new String("second"); - Object v2 = new String("val1"); - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - assertJRefCount(out, 1); - trace("testMapValueNoRefNoValueType jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapValueRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - Object v1 = new String("val1"); - String k2 = new String("second"); - // second value refs first - Object v2 = v1; - Map mi = Map.of(k1, v1, k2, v2); - String out = mapper.writeValueAsString(mi); - assertJRefCount(out, 1); - trace("testMapValueRefNoValueType jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } - - @Test - public void testMapValueMultRefNoValueType() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - String k1 = new String("first"); - Object v1 = new String("val1"); - String k2 = new String("second"); - // second value refs first - Object v2 = v1; - String k3 = "third"; - // third value refs first - Object v3 = v1; - String k4 = "fourth"; - Object v4 = v1; - Map mi = Map.of(k1, v1, k2, v2, k3, v3, k4, v4); - String out = mapper.writeValueAsString(mi); - assertJRefCount(out, 3); - trace("testMapValueMultRefNoValueType jrefserialized=", out); - Map mo = mapper.readValue(out, Map.class); - assertEquals(mi, mo); - } + @Test + public void testMapKeyNoRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + String v2 = new String("first"); + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 0); + trace("testMapKeyNoRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapKeyRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first key, but serialization treats + // it like separate string, since map keys cannot be jrefs + String v2 = k1; + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + // Because the reference is a key, it should not be serialized to jref + // so count is expected to be 0 + assertJRefCount(out, 0); + trace("testMapKeyRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueNoRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + String v2 = new String("val1"); + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 1); + trace("testMapValueNoRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + String v2 = v1; + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 1); + trace("testMapValueRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueMultRef() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + String v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + String v2 = v1; + String k3 = "third"; + // third value refs first + String v3 = v1; + String k4 = "fourth"; + String v4 = v1; + Map mi = Map.of(k1, v1, k2, v2, k3, v3, k4, v4); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 3); + trace("testMapValueMultRef jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapKeyNoRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + Object v2 = new String("first"); + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 0); + trace("testMapKeyNoRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapKeyRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first key, but serialization treats + // it like separate string, since map keys cannot be jrefs + Object v2 = k1; + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + // Because the reference is a key, it should not be serialized to jref + // so count is expected to be 0 + assertJRefCount(out, 0); + trace("testMapKeyRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueNoRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + Object v2 = new String("val1"); + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 1); + trace("testMapValueNoRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + Object v2 = v1; + Map mi = Map.of(k1, v1, k2, v2); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 1); + trace("testMapValueRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } + + @Test + public void testMapValueMultRefNoValueType() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + String k1 = new String("first"); + Object v1 = new String("val1"); + String k2 = new String("second"); + // second value refs first + Object v2 = v1; + String k3 = "third"; + // third value refs first + Object v3 = v1; + String k4 = "fourth"; + Object v4 = v1; + Map mi = Map.of(k1, v1, k2, v2, k3, v3, k4, v4); + String out = mapper.writeValueAsString(mi); + assertJRefCount(out, 3); + trace("testMapValueMultRefNoValueType jrefserialized=", out); + Map mo = mapper.readValue(out, Map.class); + assertEquals(mi, mo); + } } diff --git a/src/test/java/tools/jackson/databind/jref/JRefNestedTypeTest.java b/src/test/java/tools/jackson/databind/jref/JRefNestedTypeTest.java index 0217aa41bd..172d500c76 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefNestedTypeTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefNestedTypeTest.java @@ -12,231 +12,231 @@ public class JRefNestedTypeTest extends JRefAbstractTest { - record TreeNode(TreeNode parent, String name, String data) { - } - - private static final String ADDRESS = "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\r\n" - + "\r\n" - + "Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\r\n" - + "\r\n" - + "But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth."; - - TreeNode[] buildTwoLevelTopArray() { - TreeNode topNode = new TreeNode(null, "top", ADDRESS); - // Create three children - TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); - TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); - TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); - // Put top and all nodes in array - return new TreeNode[] { topNode, firstChild, secondChild, thirdChild }; - } - - TreeNode[] buildTwoLevelNoTopArray() { - TreeNode topNode = new TreeNode(null, "top", ADDRESS); - // Create three children - TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); - TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); - TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); - // Put child nodes in array - return new TreeNode[] { firstChild, secondChild, thirdChild }; - } - - static String JREF_JSON = "[ {\r\n" + " \"parent\" : null,\r\n" + " \"name\" : \"top\",\r\n" - + " \"data\" : \"Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\\r\\n\\r\\nNow we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\\r\\n\\r\\nBut, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth.\"\r\n" - + "}, {\r\n" + " \"parent\" : {\r\n" + " \"$ref\" : \"#/0\"\r\n" + " },\r\n" - + " \"name\" : \"child1\",\r\n" + " \"data\" : \"data1\"\r\n" + "}, {\r\n" + " \"parent\" : {\r\n" - + " \"$ref\" : \"#/0\"\r\n" + " },\r\n" + " \"name\" : \"child2\",\r\n" + " \"data\" : \"data2\"\r\n" - + "}, {\r\n" + " \"parent\" : {\r\n" + " \"$ref\" : \"#/0\"\r\n" + " },\r\n" - + " \"name\" : \"child3\",\r\n" + " \"data\" : \"data3\"\r\n" + "} ]"; - - @Test - void testDeerializeTwoLevelTreeThreeChildrenJRef() { - ObjectMapper mapper = buildObjectMapperJRef(); - TreeNode[] nodes = mapper.readValue(JREF_JSON, TreeNode[].class); - assertEquals(nodes[0], nodes[1].parent); - assertEquals(nodes[0], nodes[2].parent); - assertEquals(nodes[0], nodes[3].parent); - } - - @Test - void testTwoLevelTreeNoJRefNoTop() { - TreeNode[] nodes = buildTwoLevelNoTopArray(); - ObjectMapper mapper = buildObjectMapperNoJRef(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); - // two jrefs - assertJRefCount(json, 0); - trace("testTwoLevelTreeNoJRefNoTop json=", json); - TreeNode[] out = mapper.readValue(json, TreeNode[].class); - assertTrue(out.length == 3); - assertEquals(out[0].parent, out[1].parent); - assertEquals(out[0].parent, out[2].parent); - } - - @Test - void testTwoLevelTreeNoJRefWithTop() { - TreeNode[] nodes = buildTwoLevelTopArray(); - // Object mapper without JRefModule - ObjectMapper mapper = buildObjectMapperNoJRef(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); - // No jrefs - assertJRefCount(json, 0); - trace("testTwoLevelTreeNoJRef json=", json); - TreeNode[] out = mapper.readValue(json, TreeNode[].class); - assertEquals(out.length, 4); - assertEquals(out[0], out[1].parent); - assertEquals(out[0], out[2].parent); - assertEquals(out[0], out[3].parent); - } - - @Test - void testTwoLevelTreeJRefNoTop() { - TreeNode[] nodes = buildTwoLevelNoTopArray(); - ObjectMapper mapper = buildObjectMapperJRef(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); - // two jrefs - assertJRefCount(json, 2); - trace("testTwoLevelTreeNoJRefNoTop json=", json); - TreeNode[] out = mapper.readValue(json, TreeNode[].class); - assertTrue(out.length == 3); - assertEquals(out[0].parent, out[1].parent); - assertEquals(out[0].parent, out[2].parent); - } - - @Test - void testTwoLevelTreeJRefWithTop() { - TreeNode[] nodes = buildTwoLevelTopArray(); - // Object mapper without JRefModule - ObjectMapper mapper = buildObjectMapperJRef(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); - // No jrefs - assertJRefCount(json, 3); - trace("testTwoLevelTreeNoJRef json=", json); - TreeNode[] out = mapper.readValue(json, TreeNode[].class); - assertEquals(out.length, 4); - assertEquals(out[0], out[1].parent); - assertEquals(out[0], out[2].parent); - assertEquals(out[0], out[3].parent); - } - - TreeNode[] buildThreeLevelArray() { - TreeNode topNode = new TreeNode(null, "top", ADDRESS); - // Create three children - TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); - TreeNode firstGChild = new TreeNode(firstChild, "gchild1", "data1g"); - TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); - TreeNode secondGChild = new TreeNode(secondChild, "gchild2", "data2g"); - TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); - TreeNode thirdGChild = new TreeNode(thirdChild, "child", "data3g"); - - // Put grandchildren nodes in array - return new TreeNode[] { firstGChild, secondGChild, thirdGChild }; - } - - @Test - void testThreeLevelTree() { - TreeNode[] nodes = buildThreeLevelArray(); - ObjectMapper mapper = buildObjectMapperJRef(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); - // two jrefs - assertJRefCount(json, 2); - trace("testThreeLevelTree json=", json); - TreeNode[] out = mapper.readValue(json, TreeNode[].class); - assertTrue(out.length == 3); - assertEquals(out[0].parent.parent, out[1].parent.parent); - assertEquals(out[0].parent.parent, out[2].parent.parent); - } - - List buildThreeLevelArrayAsList() { - TreeNode topNode = new TreeNode(null, "top", ADDRESS); - // Create three children - TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); - TreeNode firstGChild = new TreeNode(firstChild, "gchild1", "data1g"); - TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); - TreeNode secondGChild = new TreeNode(secondChild, "gchild2", "data2g"); - TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); - TreeNode thirdGChild = new TreeNode(thirdChild, "child", "data3g"); - - // Put grandchildren nodes in List - return List.of(firstGChild, secondGChild, thirdGChild); - } - - record TreeNodeList(List nodes) { - } - - @Test - void testThreeLevelTreeNodeListRecord() { - List nodes = buildThreeLevelArrayAsList(); - TreeNodeList tnl = new TreeNodeList(nodes); - ObjectMapper mapper = buildObjectMapperJRef(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tnl); - // two jrefs - assertJRefCount(json, 2); - trace("testThreeLevelTreeNodeListRecord json=", json); - TreeNodeList out = mapper.readValue(json, TreeNodeList.class); - assertTrue(out.nodes().size() == 3); - assertEquals(out.nodes().get(0).parent.parent, out.nodes().get(1).parent.parent); - assertEquals(out.nodes().get(0).parent.parent, out.nodes().get(2).parent.parent); - } - - static class TreeNodeListClass { - - public List nodes; - - } - - @Test - void testThreeLevelTreeNodeListClass() { - TreeNodeListClass tnlc = new TreeNodeListClass(); - tnlc.nodes = buildThreeLevelArrayAsList(); - ObjectMapper mapper = buildObjectMapperJRef(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tnlc); - // two jrefs - assertJRefCount(json, 2); - trace("testThreeLevelTreeNodeListClass json=", json); - TreeNodeListClass out = mapper.readValue(json, TreeNodeListClass.class); - assertTrue(out.nodes.size() == 3); - assertEquals(out.nodes.get(0).parent.parent, out.nodes.get(1).parent.parent); - assertEquals(out.nodes.get(0).parent.parent, out.nodes.get(2).parent.parent); - } - - Map buildThreeLevelArrayAsMap() { - TreeNode topNode = new TreeNode(null, "top", ADDRESS); - // Create three children - TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); - TreeNode firstGChild = new TreeNode(firstChild, "gchild1", "data1g"); - TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); - TreeNode secondGChild = new TreeNode(secondChild, "gchild2", "data2g"); - TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); - TreeNode thirdGChild = new TreeNode(thirdChild, "child", "data3g"); - - // Put grandchildren nodes in Map - return Map.of(firstGChild.name, firstGChild, secondGChild.name, secondGChild, thirdGChild.name, thirdGChild); - } - - record TreeNodeMapRecord(Map nodes) { - } - - @Test - void testThreeLevelTreeNodeMapRecord() { - TreeNodeMapRecord tnmr = new TreeNodeMapRecord(buildThreeLevelArrayAsMap()); - ObjectMapper mapper = buildObjectMapperJRef(); - String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tnmr); - // two jrefs - assertJRefCount(json, 2); - trace("testThreeLevelTreeNodeMapRecord json=", json); - TreeNodeMapRecord out = mapper.readValue(json, TreeNodeMapRecord.class); - assertTrue(out.nodes.size() == 3); - out.nodes().forEach((k, v) -> { - assertEquals(k, v.name); - }); - TreeNode first = null; - for (Map.Entry entry : out.nodes().entrySet()) { - if (first == null) { - first = entry.getValue(); - } else { - assertEquals(first.parent.parent, entry.getValue().parent.parent); - } - } - } + record TreeNode(TreeNode parent, String name, String data) { + } + + private static final String ADDRESS = "Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\r\n" + + "\r\n" + + "Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\r\n" + + "\r\n" + + "But, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth."; + + TreeNode[] buildTwoLevelTopArray() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + // Put top and all nodes in array + return new TreeNode[] { topNode, firstChild, secondChild, thirdChild }; + } + + TreeNode[] buildTwoLevelNoTopArray() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + // Put child nodes in array + return new TreeNode[] { firstChild, secondChild, thirdChild }; + } + + static String JREF_JSON = "[ {\r\n" + " \"parent\" : null,\r\n" + " \"name\" : \"top\",\r\n" + + " \"data\" : \"Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.\\r\\n\\r\\nNow we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.\\r\\n\\r\\nBut, in a larger sense, we can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth.\"\r\n" + + "}, {\r\n" + " \"parent\" : {\r\n" + " \"$ref\" : \"#/0\"\r\n" + " },\r\n" + + " \"name\" : \"child1\",\r\n" + " \"data\" : \"data1\"\r\n" + "}, {\r\n" + " \"parent\" : {\r\n" + + " \"$ref\" : \"#/0\"\r\n" + " },\r\n" + " \"name\" : \"child2\",\r\n" + " \"data\" : \"data2\"\r\n" + + "}, {\r\n" + " \"parent\" : {\r\n" + " \"$ref\" : \"#/0\"\r\n" + " },\r\n" + + " \"name\" : \"child3\",\r\n" + " \"data\" : \"data3\"\r\n" + "} ]"; + + @Test + void testDeerializeTwoLevelTreeThreeChildrenJRef() { + ObjectMapper mapper = buildObjectMapperJRef(); + TreeNode[] nodes = mapper.readValue(JREF_JSON, TreeNode[].class); + assertEquals(nodes[0], nodes[1].parent); + assertEquals(nodes[0], nodes[2].parent); + assertEquals(nodes[0], nodes[3].parent); + } + + @Test + void testTwoLevelTreeNoJRefNoTop() { + TreeNode[] nodes = buildTwoLevelNoTopArray(); + ObjectMapper mapper = buildObjectMapperNoJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // two jrefs + assertJRefCount(json, 0); + trace("testTwoLevelTreeNoJRefNoTop json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertTrue(out.length == 3); + assertEquals(out[0].parent, out[1].parent); + assertEquals(out[0].parent, out[2].parent); + } + + @Test + void testTwoLevelTreeNoJRefWithTop() { + TreeNode[] nodes = buildTwoLevelTopArray(); + // Object mapper without JRefModule + ObjectMapper mapper = buildObjectMapperNoJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // No jrefs + assertJRefCount(json, 0); + trace("testTwoLevelTreeNoJRef json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertEquals(out.length, 4); + assertEquals(out[0], out[1].parent); + assertEquals(out[0], out[2].parent); + assertEquals(out[0], out[3].parent); + } + + @Test + void testTwoLevelTreeJRefNoTop() { + TreeNode[] nodes = buildTwoLevelNoTopArray(); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // two jrefs + assertJRefCount(json, 2); + trace("testTwoLevelTreeNoJRefNoTop json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertTrue(out.length == 3); + assertEquals(out[0].parent, out[1].parent); + assertEquals(out[0].parent, out[2].parent); + } + + @Test + void testTwoLevelTreeJRefWithTop() { + TreeNode[] nodes = buildTwoLevelTopArray(); + // Object mapper without JRefModule + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // No jrefs + assertJRefCount(json, 3); + trace("testTwoLevelTreeNoJRef json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertEquals(out.length, 4); + assertEquals(out[0], out[1].parent); + assertEquals(out[0], out[2].parent); + assertEquals(out[0], out[3].parent); + } + + TreeNode[] buildThreeLevelArray() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode firstGChild = new TreeNode(firstChild, "gchild1", "data1g"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode secondGChild = new TreeNode(secondChild, "gchild2", "data2g"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + TreeNode thirdGChild = new TreeNode(thirdChild, "child", "data3g"); + + // Put grandchildren nodes in array + return new TreeNode[] { firstGChild, secondGChild, thirdGChild }; + } + + @Test + void testThreeLevelTree() { + TreeNode[] nodes = buildThreeLevelArray(); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(nodes); + // two jrefs + assertJRefCount(json, 2); + trace("testThreeLevelTree json=", json); + TreeNode[] out = mapper.readValue(json, TreeNode[].class); + assertTrue(out.length == 3); + assertEquals(out[0].parent.parent, out[1].parent.parent); + assertEquals(out[0].parent.parent, out[2].parent.parent); + } + + List buildThreeLevelArrayAsList() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode firstGChild = new TreeNode(firstChild, "gchild1", "data1g"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode secondGChild = new TreeNode(secondChild, "gchild2", "data2g"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + TreeNode thirdGChild = new TreeNode(thirdChild, "child", "data3g"); + + // Put grandchildren nodes in List + return List.of(firstGChild, secondGChild, thirdGChild); + } + + record TreeNodeList(List nodes) { + } + + @Test + void testThreeLevelTreeNodeListRecord() { + List nodes = buildThreeLevelArrayAsList(); + TreeNodeList tnl = new TreeNodeList(nodes); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tnl); + // two jrefs + assertJRefCount(json, 2); + trace("testThreeLevelTreeNodeListRecord json=", json); + TreeNodeList out = mapper.readValue(json, TreeNodeList.class); + assertTrue(out.nodes().size() == 3); + assertEquals(out.nodes().get(0).parent.parent, out.nodes().get(1).parent.parent); + assertEquals(out.nodes().get(0).parent.parent, out.nodes().get(2).parent.parent); + } + + static class TreeNodeListClass { + + public List nodes; + + } + + @Test + void testThreeLevelTreeNodeListClass() { + TreeNodeListClass tnlc = new TreeNodeListClass(); + tnlc.nodes = buildThreeLevelArrayAsList(); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tnlc); + // two jrefs + assertJRefCount(json, 2); + trace("testThreeLevelTreeNodeListClass json=", json); + TreeNodeListClass out = mapper.readValue(json, TreeNodeListClass.class); + assertTrue(out.nodes.size() == 3); + assertEquals(out.nodes.get(0).parent.parent, out.nodes.get(1).parent.parent); + assertEquals(out.nodes.get(0).parent.parent, out.nodes.get(2).parent.parent); + } + + Map buildThreeLevelArrayAsMap() { + TreeNode topNode = new TreeNode(null, "top", ADDRESS); + // Create three children + TreeNode firstChild = new TreeNode(topNode, "child1", "data1"); + TreeNode firstGChild = new TreeNode(firstChild, "gchild1", "data1g"); + TreeNode secondChild = new TreeNode(topNode, "child2", "data2"); + TreeNode secondGChild = new TreeNode(secondChild, "gchild2", "data2g"); + TreeNode thirdChild = new TreeNode(topNode, "child3", "data3"); + TreeNode thirdGChild = new TreeNode(thirdChild, "child", "data3g"); + + // Put grandchildren nodes in Map + return Map.of(firstGChild.name, firstGChild, secondGChild.name, secondGChild, thirdGChild.name, thirdGChild); + } + + record TreeNodeMapRecord(Map nodes) { + } + + @Test + void testThreeLevelTreeNodeMapRecord() { + TreeNodeMapRecord tnmr = new TreeNodeMapRecord(buildThreeLevelArrayAsMap()); + ObjectMapper mapper = buildObjectMapperJRef(); + String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tnmr); + // two jrefs + assertJRefCount(json, 2); + trace("testThreeLevelTreeNodeMapRecord json=", json); + TreeNodeMapRecord out = mapper.readValue(json, TreeNodeMapRecord.class); + assertTrue(out.nodes.size() == 3); + out.nodes().forEach((k, v) -> { + assertEquals(k, v.name); + }); + TreeNode first = null; + for (Map.Entry entry : out.nodes().entrySet()) { + if (first == null) { + first = entry.getValue(); + } else { + assertEquals(first.parent.parent, entry.getValue().parent.parent); + } + } + } } diff --git a/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java b/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java index 3535d1d13c..11ef9e8fe7 100644 --- a/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java +++ b/src/test/java/tools/jackson/databind/jref/JRefRecordBeanTest.java @@ -14,87 +14,87 @@ public class JRefRecordBeanTest extends JRefAbstractTest { - static record IntBean(Integer j) { - } - - @Test - public void testIntBeanArray() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - IntBean i1 = new IntBean(10); - IntBean i2 = new IntBean(20); - IntBean[] beans = new IntBean[] { i1, i2, i1, i2 }; - String out = mapper.writeValueAsString(beans); - trace("testIntBeanArray", out); - IntBean[] result = mapper.readValue(out, IntBean[].class); - assertEquals(result[0], result[2]); - assertEquals(result[1], result[3]); - } - - @Test - public void testIntBeanList() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - IntBean i1 = new IntBean(10); - IntBean i2 = new IntBean(20); - List beans = List.of(i1, i2, i1, i2); - String out = mapper.writeValueAsString(beans); - trace("testIntBeanList", out); - @SuppressWarnings("unchecked") - List result = (List) mapper.readValue(out, List.class); - assertEquals(result.get(0), result.get(2)); - assertEquals(result.get(1), result.get(3)); - } - - record StringKeyBeanMap(Map items) { - } - - @Test - public void testStringKeyMap() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - IntBean i1 = new IntBean(10); - IntBean i2 = new IntBean(20); - StringKeyBeanMap beanMap = new StringKeyBeanMap(Map.of("one", i1, "two", i2, "three", i1, "four", i2)); - String out = mapper.writeValueAsString(beanMap); - trace("testIntBeanStringKeyMap", out); - StringKeyBeanMap result = mapper.readValue(out, StringKeyBeanMap.class); - assertEquals(result.items().get("one"), result.items().get("three")); - assertEquals(result.items().get("two"), result.items().get("four")); - } - - record Node(@JsonProperty Node parent, @JsonProperty String name) { - } - - record NodeList(@JsonProperty List nodes) { - - } - - @Test - public void testNodeTree() throws Exception { - ObjectMapper mapper = buildObjectMapperJRef(); - Node root = new Node(null, "root"); - String[] nodeNames = new String[] { "child1", "child2", "child3" }; - List nodeList = new ArrayList<>(); - for (int i = 0; i < nodeNames.length; i++) { - nodeList.add(new Node(root, nodeNames[i])); - } - // Add references to previously added nodes in reverse order - nodeList.add(nodeList.get(2)); - nodeList.add(nodeList.get(1)); - nodeList.add(nodeList.get(0)); - String out = mapper.writeValueAsString(new NodeList(nodeList)); - trace("testNodeTree", out); - NodeList result = mapper.readValue(out, NodeList.class); - List nodes = result.nodes(); - // assert nodes list same as input - assertEquals(nodeList.size(), nodes.size()); - for (int firstIndex = 0; firstIndex < nodeList.size() / 2; firstIndex++) { - int secondIndex = (nodeList.size() - 1) - firstIndex; - Node n1 = result.nodes().get(firstIndex); - assertEquals(root.name(), n1.parent().name()); - Node n2 = result.nodes().get(secondIndex); - assertEquals(root.name(), n2.parent().name()); - assertEquals(n1, n2); - - } - } + static record IntBean(Integer j) { + } + + @Test + public void testIntBeanArray() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + IntBean i1 = new IntBean(10); + IntBean i2 = new IntBean(20); + IntBean[] beans = new IntBean[] { i1, i2, i1, i2 }; + String out = mapper.writeValueAsString(beans); + trace("testIntBeanArray", out); + IntBean[] result = mapper.readValue(out, IntBean[].class); + assertEquals(result[0], result[2]); + assertEquals(result[1], result[3]); + } + + @Test + public void testIntBeanList() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + IntBean i1 = new IntBean(10); + IntBean i2 = new IntBean(20); + List beans = List.of(i1, i2, i1, i2); + String out = mapper.writeValueAsString(beans); + trace("testIntBeanList", out); + @SuppressWarnings("unchecked") + List result = (List) mapper.readValue(out, List.class); + assertEquals(result.get(0), result.get(2)); + assertEquals(result.get(1), result.get(3)); + } + + record StringKeyBeanMap(Map items) { + } + + @Test + public void testStringKeyMap() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + IntBean i1 = new IntBean(10); + IntBean i2 = new IntBean(20); + StringKeyBeanMap beanMap = new StringKeyBeanMap(Map.of("one", i1, "two", i2, "three", i1, "four", i2)); + String out = mapper.writeValueAsString(beanMap); + trace("testIntBeanStringKeyMap", out); + StringKeyBeanMap result = mapper.readValue(out, StringKeyBeanMap.class); + assertEquals(result.items().get("one"), result.items().get("three")); + assertEquals(result.items().get("two"), result.items().get("four")); + } + + record Node(@JsonProperty Node parent, @JsonProperty String name) { + } + + record NodeList(@JsonProperty List nodes) { + + } + + @Test + public void testNodeTree() throws Exception { + ObjectMapper mapper = buildObjectMapperJRef(); + Node root = new Node(null, "root"); + String[] nodeNames = new String[] { "child1", "child2", "child3" }; + List nodeList = new ArrayList<>(); + for (int i = 0; i < nodeNames.length; i++) { + nodeList.add(new Node(root, nodeNames[i])); + } + // Add references to previously added nodes in reverse order + nodeList.add(nodeList.get(2)); + nodeList.add(nodeList.get(1)); + nodeList.add(nodeList.get(0)); + String out = mapper.writeValueAsString(new NodeList(nodeList)); + trace("testNodeTree", out); + NodeList result = mapper.readValue(out, NodeList.class); + List nodes = result.nodes(); + // assert nodes list same as input + assertEquals(nodeList.size(), nodes.size()); + for (int firstIndex = 0; firstIndex < nodeList.size() / 2; firstIndex++) { + int secondIndex = (nodeList.size() - 1) - firstIndex; + Node n1 = result.nodes().get(firstIndex); + assertEquals(root.name(), n1.parent().name()); + Node n2 = result.nodes().get(secondIndex); + assertEquals(root.name(), n2.parent().name()); + assertEquals(n1, n2); + + } + } } From 44048828a3fd8ffdca0e4f186b5384108efab475 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 17 Jul 2026 17:26:23 -0700 Subject: [PATCH 7/8] Fix unit test access issue wrt module-info.java --- src/test/java/module-info.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/module-info.java b/src/test/java/module-info.java index fd5530cf02..46e6501ff9 100644 --- a/src/test/java/module-info.java +++ b/src/test/java/module-info.java @@ -82,6 +82,7 @@ opens tools.jackson.databind.ext.xml; opens tools.jackson.databind.format; opens tools.jackson.databind.interop; + opens tools.jackson.databind.jref; opens tools.jackson.databind.jsonschema; opens tools.jackson.databind.jsontype.deduct; opens tools.jackson.databind.jsontype.deftyping; From ebd12d3f98d3133b954cb6461b93b1c53fc14683 Mon Sep 17 00:00:00 2001 From: Scott Lewis Date: Fri, 24 Jul 2026 18:43:17 -0700 Subject: [PATCH 8/8] This commit fixes the toString()-based JsonPointer comparison issue frm line 206-207 of JRefModule discussed starting with this comment: https://github.com/FasterXML/jackson-databind/pull/6045#issuecomment-5008831112 The fix is to use the newly merged JsonPointer.startsWith(JsonPointer other) method added in jackson-core via pr this pr: https://github.com/FasterXML/jackson-core/pull/1636 This means that the latest of Jackson-core 3.x branch will be required to compile the updated JRefModule. --- src/main/java/tools/jackson/databind/JRefModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/tools/jackson/databind/JRefModule.java b/src/main/java/tools/jackson/databind/JRefModule.java index afcf5231f6..11e7896a60 100644 --- a/src/main/java/tools/jackson/databind/JRefModule.java +++ b/src/main/java/tools/jackson/databind/JRefModule.java @@ -203,7 +203,7 @@ Object jrefDeserialize(JsonParser p, DeserializationContext ctxt, Deserializer d } JsonPointer ctxtPtr = JsonPointer.forPath(p.streamReadContext(), false); // build currPtr from context and parent - JsonPointer currPtr = ctxtPtr.toString().startsWith(parentPtr.toString()) ? ctxtPtr + JsonPointer currPtr = ctxtPtr.startsWith(parentPtr) ? ctxtPtr : parentPtr.append(ctxtPtr); ptrStack.push(currPtr); Object result = null;