Skip to content

Commit f20417f

Browse files
committed
Fix #1516: Support @JsonManagedReference on creator properties
1 parent 44900ef commit f20417f

File tree

5 files changed

+153
-25
lines changed

5 files changed

+153
-25
lines changed

src/main/java/tools/jackson/databind/deser/BasicDeserializerFactory.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,13 @@ protected SettableBeanProperty constructCreatorProperty(DeserializationContext c
602602
SettableBeanProperty prop = CreatorProperty.construct(name, type, property.getWrapperName(),
603603
typeDeser, beanDescRef.getClassAnnotations(), param, index, injectable,
604604
metadata);
605+
// [databind#1516]: Handle @JsonManagedReference for creator properties
606+
if (intr != null) {
607+
AnnotationIntrospector.ReferenceProperty ref = intr.findReferenceType(config, param);
608+
if (ref != null && ref.isManagedReference()) {
609+
prop.setManagedReferenceName(ref.getName());
610+
}
611+
}
605612
ValueDeserializer<?> deser = findDeserializerFromAnnotation(ctxt, param);
606613
if (deser == null) {
607614
deser = (ValueDeserializer<?>) type.getValueHandler();

src/main/java/tools/jackson/databind/deser/bean/BeanDeserializer.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010
import tools.jackson.databind.deser.ReadableObjectId.Referring;
1111
import tools.jackson.databind.deser.SettableBeanProperty;
1212
import tools.jackson.databind.deser.UnresolvedForwardReference;
13-
import tools.jackson.databind.deser.impl.ExternalTypeHandler;
14-
import tools.jackson.databind.deser.impl.MethodProperty;
15-
import tools.jackson.databind.deser.impl.ObjectIdReader;
16-
import tools.jackson.databind.deser.impl.UnwrappedPropertyHandler;
13+
import tools.jackson.databind.deser.impl.*;
1714
import tools.jackson.databind.util.ClassUtil;
1815
import tools.jackson.databind.util.IgnorePropertiesUtil;
1916
import tools.jackson.databind.util.NameTransformer;
@@ -679,6 +676,17 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
679676
} catch (Exception e) {
680677
return wrapInstantiationProblem(ctxt, e);
681678
}
679+
680+
// [databind#1516]: Inject back references for managed reference creator properties
681+
for (SettableBeanProperty prop : creator.properties()) {
682+
if (prop instanceof ManagedReferenceProperty managedProp) {
683+
Object value = buffer.getParameter(ctxt, prop);
684+
if (value != null) {
685+
managedProp.injectBackReference(ctxt, bean, value);
686+
}
687+
}
688+
}
689+
682690
p.assignCurrentValue(bean);
683691
// [databind#4938] Since 2.19, allow returning `null` from creator,
684692
// but if so, need to skip all possibly relevant content

src/main/java/tools/jackson/databind/deser/impl/ManagedReferenceProperty.java

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -76,31 +76,63 @@ public final void set(DeserializationContext ctxt, Object instance, Object value
7676
@Override
7777
public Object setAndReturn(DeserializationContext ctxt, Object instance, Object value)
7878
{
79+
_setBackReference(ctxt, instance, value);
80+
// and then the forward reference itself
81+
return delegate.setAndReturn(ctxt, instance, value);
82+
}
83+
84+
/**
85+
* Inject back reference into value without setting forward reference.
86+
* Used for creator properties where forward reference is already set via constructor.
87+
*
88+
* @since 3.0
89+
*/
90+
public void injectBackReference(DeserializationContext ctxt, Object bean, Object value)
91+
{
92+
_setBackReference(ctxt, bean, value);
93+
}
94+
95+
/*
96+
/**********************************************************************
97+
/* Helper classes
98+
/**********************************************************************
99+
*/
100+
101+
/**
102+
* Helper method to inject back reference into value(s).
103+
*/
104+
private void _setBackReference(DeserializationContext ctxt, Object instance, Object value)
105+
{
106+
if (value == null) {
107+
return;
108+
}
79109
// 04-Feb-2014, tatu: As per [#390], it may be necessary to switch the
80110
// ordering of forward/backward references, and start with back ref.
81-
if (value != null) {
82-
if (_isContainer) { // ok, this gets ugly... but has to do for now
83-
if (value instanceof Object[]) {
84-
for (Object ob : (Object[]) value) {
85-
if (ob != null) { _backProperty.set(ctxt, ob, instance); }
111+
if (_isContainer) { // ok, this gets ugly... but has to do for now
112+
if (value instanceof Object[]) {
113+
for (Object ob : (Object[]) value) {
114+
if (ob != null) {
115+
_backProperty.set(ctxt, ob, instance);
86116
}
87-
} else if (value instanceof Collection<?>) {
88-
for (Object ob : (Collection<?>) value) {
89-
if (ob != null) { _backProperty.set(ctxt, ob, instance); }
117+
}
118+
} else if (value instanceof Collection<?>) {
119+
for (Object ob : (Collection<?>) value) {
120+
if (ob != null) {
121+
_backProperty.set(ctxt, ob, instance);
90122
}
91-
} else if (value instanceof Map<?,?>) {
92-
for (Object ob : ((Map<?,?>) value).values()) {
93-
if (ob != null) { _backProperty.set(ctxt, ob, instance); }
123+
}
124+
} else if (value instanceof Map<?,?>) {
125+
for (Object ob : ((Map<?,?>) value).values()) {
126+
if (ob != null) {
127+
_backProperty.set(ctxt, ob, instance);
94128
}
95-
} else {
96-
throw new IllegalStateException("Unsupported container type ("+value.getClass().getName()
97-
+") when resolving reference '"+_referenceName+"'");
98129
}
99130
} else {
100-
_backProperty.set(ctxt, value, instance);
131+
throw new IllegalStateException("Unsupported container type (" + value.getClass().getName()
132+
+ ") when resolving reference '" + _referenceName + "'");
101133
}
134+
} else {
135+
_backProperty.set(ctxt, value, instance);
102136
}
103-
// and then the forward reference itself
104-
return delegate.setAndReturn(ctxt, instance, value);
105-
}
137+
}
106138
}

src/test/java/tools/jackson/databind/tofix/BackReference1516Test.java renamed to src/test/java/tools/jackson/databind/struct/BackReference1516Test.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tools.jackson.databind.tofix;
1+
package tools.jackson.databind.struct;
22

33
import java.beans.ConstructorProperties;
44

@@ -9,7 +9,6 @@
99

1010
import tools.jackson.databind.ObjectMapper;
1111
import tools.jackson.databind.testutil.DatabindTestUtil;
12-
import tools.jackson.databind.testutil.failure.JacksonTestFailureExpected;
1312

1413
import static org.junit.jupiter.api.Assertions.assertNotNull;
1514
import static org.junit.jupiter.api.Assertions.assertSame;
@@ -75,7 +74,6 @@ public ChildObject2(String id, String name,
7574
" 'child': { 'id': 'def', 'name':'Bert' }\n" +
7675
"}");
7776

78-
@JacksonTestFailureExpected
7977
@Test
8078
void withParentCreator() throws Exception {
8179
ParentWithCreator result = MAPPER.readValue(PARENT_CHILD_JSON,
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package tools.jackson.databind.struct;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
import com.fasterxml.jackson.annotation.JsonBackReference;
9+
import com.fasterxml.jackson.annotation.JsonCreator;
10+
import com.fasterxml.jackson.annotation.JsonManagedReference;
11+
import com.fasterxml.jackson.annotation.JsonProperty;
12+
13+
import tools.jackson.databind.ObjectMapper;
14+
import tools.jackson.databind.testutil.DatabindTestUtil;
15+
16+
import static org.junit.jupiter.api.Assertions.*;
17+
18+
// [databind#1516]
19+
public class TestKotlinGithub129 extends DatabindTestUtil
20+
{
21+
private final ObjectMapper MAPPER = newJsonMapper();
22+
23+
static class Car {
24+
private final long id;
25+
26+
@JsonManagedReference
27+
private final List<Color> colors;
28+
29+
@JsonCreator
30+
public Car(@JsonProperty("id") long id,
31+
@JsonProperty("colors") List<Color> colors) {
32+
this.id = id;
33+
this.colors = colors != null ? colors : new ArrayList<>();
34+
}
35+
36+
public long getId() { return id; }
37+
public List<Color> getColors() { return colors; }
38+
}
39+
40+
static class Color {
41+
private final long id;
42+
private final String code;
43+
44+
@JsonBackReference
45+
private Car car;
46+
47+
@JsonCreator
48+
public Color(@JsonProperty("id") long id,
49+
@JsonProperty("code") String code) {
50+
this.id = id;
51+
this.code = code;
52+
}
53+
54+
public long getId() { return id; }
55+
public String getCode() { return code; }
56+
public Car getCar() { return car; }
57+
public void setCar(Car car) { this.car = car; }
58+
}
59+
60+
@Test
61+
public void testManagedReferenceOnCreator() throws Exception
62+
{
63+
Car car = new Car(100, new ArrayList<>());
64+
Color color = new Color(100, "#FFFFF");
65+
color.setCar(car);
66+
car.getColors().add(color);
67+
68+
String json = MAPPER.writeValueAsString(car);
69+
Car result = MAPPER.readValue(json, Car.class);
70+
71+
assertNotNull(result);
72+
assertEquals(100, result.getId());
73+
assertNotNull(result.getColors());
74+
assertEquals(1, result.getColors().size());
75+
76+
Color resultColor = result.getColors().get(0);
77+
assertEquals(100, resultColor.getId());
78+
assertEquals("#FFFFF", resultColor.getCode());
79+
80+
assertNotNull(resultColor.getCar());
81+
assertSame(result, resultColor.getCar());
82+
}
83+
}

0 commit comments

Comments
 (0)