Skip to content
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
06c5bcb
Initial checkin of JRefValueDeserializer/JRefResolver
scottslewis May 20, 2026
2e59cee
Added JRefSetter and JRefSetterFunction types, simplified JRefPath and
scottslewis May 21, 2026
d7e2e6b
Removed JRefSetter and JRefSetterFunction in favor of new SetterFunction
scottslewis May 21, 2026
805fc1b
Added support in ValueDeserializerModifier for using
scottslewis May 22, 2026
962e0f2
Merge branch 'FasterXML:3.x' into jref_deserialization
scottslewis May 22, 2026
36ef70c
Added jref handling to StringCollectionDeserializer. Added test case
scottslewis May 24, 2026
fbc2d63
Added test cases for Double, Float, Boolean, and String handling in
scottslewis May 24, 2026
d038844
Added more (passing) test cases
scottslewis May 25, 2026
5fd7a24
Refactored for simplicity. Added string constants for "$ref" and
scottslewis May 25, 2026
dfe1eb2
Merge branch '3.x' into jref_deserialization
scottslewis May 26, 2026
3416b68
Refactoring for simplification.
scottslewis May 27, 2026
42efbaa
Merge branch 'jref_deserialization' of https://github.com/scottslewis…
scottslewis May 27, 2026
3854fe9
Merge branch '3.x' into jref_deserialization
scottslewis May 27, 2026
f8e8a4d
Refactoring as per discussion on pr #6014
scottslewis May 28, 2026
8589262
Merge branch 'jref_deserialization' of https://github.com/scottslewis…
scottslewis May 28, 2026
203ad96
Merge branch '3.x' into jref_deserialization
scottslewis May 28, 2026
ac4ea69
Merge branch '3.x' into jref_deserialization
scottslewis May 28, 2026
4746244
Merge branch '3.x' into jref_deserialization
cowtowncoder May 29, 2026
cfe2bbf
Fix for comment
scottslewis May 30, 2026
106a93a
Fix for comment
scottslewis May 30, 2026
26f93d9
Changed JRefResolveException into DatabindException subclass (rather
scottslewis May 30, 2026
99ed93c
Merge branch 'jref_deserialization' of https://github.com/scottslewis…
scottslewis May 30, 2026
9f4b06f
API simplification
scottslewis May 30, 2026
63b6646
Added jref tests package. Created JRefBeanPublicMemberDeserializerTest
scottslewis Jun 1, 2026
52b1ddb
Added JRefSerializer class to build jrefs from original OG
scottslewis Jun 1, 2026
b0c4a3d
Backup of node work
scottslewis Jun 6, 2026
76f8765
Added JRefValueSerializerModifier and appropriate JRefValueSerializer
scottslewis Jun 6, 2026
6e7f70b
Merge branch '3.x' into jref_deserialization
scottslewis Jun 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions src/main/java/tools/jackson/databind/JRefModule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package tools.jackson.databind;

import java.util.List;
import java.util.function.Function;

import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.BeanDescription.Supplier;
import tools.jackson.databind.deser.BeanDeserializerBuilder;
import tools.jackson.databind.deser.ValueDeserializerModifier;
import tools.jackson.databind.deser.std.DelegatingDeserializer;
import tools.jackson.databind.introspect.BeanPropertyDefinition;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.module.SimpleModule;
import tools.jackson.databind.node.ObjectNode;
import tools.jackson.databind.node.TreeTraversingParser;
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;
import tools.jackson.databind.type.ReferenceType;

public class JRefModule extends SimpleModule {

private static final long serialVersionUID = 1L;

public JRefModule() {
super("JRef");
}

protected class JRefValueDeserializerModifier extends ValueDeserializerModifier {

private static final long serialVersionUID = 1L;

protected ValueDeserializer<?> makeJRefValueDeserializer(DeserializationConfig config, JavaType jt,
Supplier beanDescRef, ValueDeserializer<?> d) {
return new JRefValueDeserializer(d);
}

@Override
public ValueDeserializer<?> modifyArrayDeserializer(DeserializationConfig config, ArrayType valueType,
Supplier beanDescRef, ValueDeserializer<?> deserializer) {
return makeJRefValueDeserializer(config, valueType, beanDescRef, deserializer);
}

@Override
public ValueDeserializer<?> modifyCollectionDeserializer(DeserializationConfig config, CollectionType type,
Supplier beanDescRef, ValueDeserializer<?> deserializer) {
return makeJRefValueDeserializer(config, type, beanDescRef, deserializer);
}

@Override
public ValueDeserializer<?> modifyEnumDeserializer(DeserializationConfig config, JavaType type,
Supplier beanDescRef, ValueDeserializer<?> deserializer) {
return makeJRefValueDeserializer(config, type, beanDescRef, deserializer);
}

@Override
public ValueDeserializer<?> modifyCollectionLikeDeserializer(DeserializationConfig config,
CollectionLikeType type, Supplier beanDescRef, ValueDeserializer<?> deserializer) {
return makeJRefValueDeserializer(config, type, beanDescRef, deserializer);
}

@Override
public ValueDeserializer<?> modifyDeserializer(DeserializationConfig config, Supplier beanDescRef,
ValueDeserializer<?> deserializer) {
return makeJRefValueDeserializer(config, null, beanDescRef, deserializer);
}

@Override
public KeyDeserializer modifyKeyDeserializer(DeserializationConfig config, JavaType type,
KeyDeserializer deserializer) {
return super.modifyKeyDeserializer(config, type, deserializer);
}

@Override
public ValueDeserializer<?> modifyMapDeserializer(DeserializationConfig config, MapType type,
Supplier beanDescRef, ValueDeserializer<?> deserializer) {
return makeJRefValueDeserializer(config, type, beanDescRef, deserializer);
}

@Override
public ValueDeserializer<?> modifyMapLikeDeserializer(DeserializationConfig config, MapLikeType type,
Supplier beanDescRef, ValueDeserializer<?> deserializer) {
return makeJRefValueDeserializer(config, type, beanDescRef, deserializer);
}

@Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, Supplier beanDescRef,
BeanDeserializerBuilder builder) {
return super.updateBuilder(config, beanDescRef, builder);
}

@Override
public List<BeanPropertyDefinition> updateProperties(DeserializationConfig config, Supplier beanDescRef,
List<BeanPropertyDefinition> propDefs) {
return super.updateProperties(config, beanDescRef, propDefs);
}

@Override
public ValueDeserializer<?> modifyReferenceDeserializer(DeserializationConfig config, ReferenceType type,
Supplier beanDescRef, ValueDeserializer<?> deserializer) {
return super.modifyReferenceDeserializer(config, type, beanDescRef, deserializer);
}
}

@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addDeserializerModifier(new JRefValueDeserializerModifier());
}

public static class JRefValueDeserializer extends DelegatingDeserializer {

void trace(String method, JRefValueDeserializer vds) {
System.out.println(new StringBuffer(method).append(".").append(vds.toString().toString()));
}

@Override
public String toString() {
return "JRefValueDeserializer[delegate=" + _delegatee + ", class=" + _valueClass + "]";
}

void trace(String method) {
trace(method, this);
}

public JRefValueDeserializer(ValueDeserializer<?> src) {
super(src);
}

protected class JRefFindResult {
JRefPath jrefPath;
JsonParser parser;
}

protected TreeTraversingParser createTreeTraversingParser(JsonNode node) {
TreeTraversingParser result = new TreeTraversingParser(node);
result.nextToken();
return result;
}

/**
* Find JRef in parser input stream.
*
* @param p JsonParser to use
* @param ctxt the current context
* @param typeDeserializer optional typeDeserializer
* @return JRefFindResult with JRefPath either set to non null (path found), or
* set to null (meaning that the further deserialization should be
* undertaken with the parse given in JRefFindResult
*/
protected JRefFindResult findJRef(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer) {
JRefFindResult result = new JRefFindResult();
JsonToken tok = p.currentToken();
if (tok == JsonToken.START_OBJECT) {
JsonNode n = ctxt.readTree(p);
if (n instanceof ObjectNode) {
ObjectNode on = (ObjectNode) n;
JsonNode valNode = on.get(JRefPath.JREF_REF);
if (valNode != null) {
String valStr = valNode.asString();
if (valStr != null) {
result.jrefPath = new JRefPath(valStr, ctxt, getDelegatee(), typeDeserializer);
result.parser = p;
return result;
}
// JREF_REF found, but no/null path. This is a syntax error
throw DatabindException.from(p, "JRefPath detected on stream but path is null", null);
}
}
result.parser = createTreeTraversingParser(n);
} else {
result.parser = p;
}
return result;
}

/**
* Deserialize with jref handling. Calls
* {@link #findJRef(JsonParser, DeserializationContext, TypeDeserializer)} to
* look on the parser stream for objects with 1 key == '$ref'. If found then the
* JRefPath is returned. If not found then the func arg is applied with the
* appropriate parser returned in JRefFindResult.parser field.
*
* @param p
* @param ctxt
* @param typeDeserializer
* @param func
* @return
*/
protected Object deserializerWithJRef(JsonParser p, DeserializationContext ctxt,
TypeDeserializer typeDeserializer, Function<JsonParser, Object> func) {
Object result = null;
JRefFindResult findResult = findJRef(p, ctxt, typeDeserializer);
if (findResult.jrefPath != null) {
result = findResult.jrefPath;
} else {
result = func.apply(findResult.parser);
}
return result;
}

@Override
public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
throws JacksonException {
return deserializerWithJRef(p, ctxt, typeDeserializer,
ps -> super.deserializeWithType(ps, ctxt, typeDeserializer));
}

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
return deserializerWithJRef(p, ctxt, null, ps -> super.deserialize(ps, ctxt));
}

@Override
protected ValueDeserializer<?> newDelegatingInstance(ValueDeserializer<?> newDelegatee) {
return new JRefValueDeserializer(newDelegatee);
}

}

}
52 changes: 52 additions & 0 deletions src/main/java/tools/jackson/databind/JRefPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package tools.jackson.databind;

import java.util.Objects;

import tools.jackson.databind.jsontype.TypeDeserializer;

public class JRefPath {

public static final String JREF_REF = "$ref";
private final DeserializationContext ctxt;
private final String path;
private final ValueDeserializer<?> deserializer;
private final TypeDeserializer typeDeserializer;

public JRefPath(String path, DeserializationContext ctxt, ValueDeserializer<?> deserializer,
TypeDeserializer typeDeserializer) {
Objects.requireNonNull(path, "path cannot be null");
this.path = path;
Objects.requireNonNull(ctxt, "ctxt cannot be null");
this.ctxt = ctxt;
Objects.requireNonNull(deserializer, "deserializer cannot be null");
this.deserializer = deserializer;
this.typeDeserializer = typeDeserializer;
}

public JRefPath(String path, DeserializationContext ctxt, ValueDeserializer<?> deserializer) {
this(path, ctxt, deserializer, null);
}

public DeserializationContext getContext() {
return this.ctxt;
}

public String getPath() {
return this.path;
}

public ValueDeserializer<?> getValueDeserializer() {
return deserializer;
}

public TypeDeserializer getTypeDeserializer() {
return typeDeserializer;
}

@Override
public String toString() {
return "JRefPath[ctxt=" + ctxt + ", path=" + path + ", deserializer=" + deserializer + ", typeDeserializer="
+ typeDeserializer + "]";
}

}
39 changes: 39 additions & 0 deletions src/main/java/tools/jackson/databind/JRefResolveException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package tools.jackson.databind;

public class JRefResolveException extends RuntimeException {
Comment thread
scottslewis marked this conversation as resolved.
Outdated

private static final long serialVersionUID = 1L;
private final JRefResolver resolver;
private final Object root;

public JRefResolveException(JRefResolver resolver, Object root, String message, Throwable cause) {
super(message, cause);
this.resolver = resolver;
this.root = root;
}

public JRefResolveException(JRefResolver resolver, Object root, String message) {
super(message);
this.resolver = resolver;
this.root = root;
}

public JRefResolveException(JRefResolver resolver, String message) {
this(resolver, null, message);
}

public JRefResolver getResolver() {
return this.resolver;
}

public Object getRoot() {
return this.root;
}

@Override
public String toString() {
return "JRefResolveException [resolver=" + resolver + ", root=" + root + ", message=" + super.getMessage()
+ "]";
}

}
Loading
Loading