Skip to content

Commit 567d6c0

Browse files
committed
Fix #1261
1 parent 522d8f6 commit 567d6c0

File tree

6 files changed

+173
-218
lines changed

6 files changed

+173
-218
lines changed

release-notes/CREDITS

+5
Original file line numberDiff line numberDiff line change
@@ -494,3 +494,8 @@ Maarten Billemont (lhunath@github)
494494
Vladimir Kulev (lightoze@github)
495495
* Reported #1028: Ignore USE_BIG_DECIMAL_FOR_FLOATS for NaN/Infinity
496496
(2.8.0)
497+
498+
Ari Fogel (arifogel@github)
499+
* Reported 1261, contributed fix for: `@JsonIdentityInfo` deserialization fails with
500+
combination of forward references, `@JsonCreator`
501+
(2.8.0)

release-notes/VERSION

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ Project: jackson-databind
4545
#1233: Add support for `JsonFormat.Feature.WRITE_SORTED_MAP_ENTRIES`
4646
#1235: `java.nio.file.Path` support incomplete
4747
(reported by, fix contributed by Benson M)
48+
#1261: JsonIdentityInfo broken deserialization involving forward references and/or cycles
49+
(reported by, fix contributed by Ari F)
4850

4951
2.7.5 (11-Jun-2016)
5052

src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializer.java

+58-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.fasterxml.jackson.core.*;
77
import com.fasterxml.jackson.databind.*;
88
import com.fasterxml.jackson.databind.deser.impl.*;
9+
import com.fasterxml.jackson.databind.deser.impl.ReadableObjectId.Referring;
910
import com.fasterxml.jackson.databind.util.NameTransformer;
1011
import com.fasterxml.jackson.databind.util.TokenBuffer;
1112

@@ -384,6 +385,7 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
384385
TokenBuffer unknown = null;
385386

386387
JsonToken t = p.getCurrentToken();
388+
List<BeanReferring> referrings = null;
387389
for (; t == JsonToken.FIELD_NAME; t = p.nextToken()) {
388390
String propName = p.getCurrentName();
389391
p.nextToken(); // to point to value
@@ -426,11 +428,21 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
426428
// regular property? needs buffering
427429
SettableBeanProperty prop = _beanProperties.find(propName);
428430
if (prop != null) {
429-
buffer.bufferProperty(prop, _deserializeWithErrorWrapping(p, ctxt, prop));
431+
try {
432+
buffer.bufferProperty(prop, _deserializeWithErrorWrapping(p, ctxt, prop));
433+
} catch (UnresolvedForwardReference reference) {
434+
// 14-Jun-2016, tatu: As per [databind#1261], looks like we need additional
435+
// handling of forward references here. Not exactly sure why existing
436+
// facilities did not cover, but this does appear to solve the problem
437+
BeanReferring referring = handleUnresolvedReference(p, prop, buffer, reference);
438+
if (referrings == null) {
439+
referrings = new ArrayList<BeanReferring>();
440+
}
441+
referrings.add(referring);
442+
}
430443
continue;
431444
}
432-
// As per [JACKSON-313], things marked as ignorable should not be
433-
// passed to any setter
445+
// Things marked as ignorable should not be passed to any setter
434446
if (_ignorableProps != null && _ignorableProps.contains(propName)) {
435447
handleIgnoredProperty(p, ctxt, handledType(), propName);
436448
continue;
@@ -460,6 +472,11 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
460472
wrapInstantiationProblem(e, ctxt);
461473
bean = null; // never gets here
462474
}
475+
if (referrings != null) {
476+
for (BeanReferring referring : referrings) {
477+
referring.setBean(bean);
478+
}
479+
}
463480
if (unknown != null) {
464481
// polymorphic?
465482
if (bean.getClass() != _beanType.getRawClass()) {
@@ -471,6 +488,20 @@ protected Object _deserializeUsingPropertyBased(final JsonParser p, final Deseri
471488
return bean;
472489
}
473490

491+
/**
492+
* @since 2.8
493+
*/
494+
private BeanReferring handleUnresolvedReference(JsonParser p,
495+
SettableBeanProperty prop, PropertyValueBuffer buffer,
496+
UnresolvedForwardReference reference)
497+
throws JsonMappingException
498+
{
499+
BeanReferring referring = new BeanReferring(reference, prop.getType().getRawClass(),
500+
buffer, prop);
501+
reference.getRoid().appendReferring(referring);
502+
return referring;
503+
}
504+
474505
protected final Object _deserializeWithErrorWrapping(JsonParser p,
475506
DeserializationContext ctxt, SettableBeanProperty prop)
476507
throws IOException
@@ -920,4 +951,28 @@ protected Exception _creatorReturnedNullException() {
920951
}
921952
return _nullFromCreator;
922953
}
954+
955+
/**
956+
* @since 2.8
957+
*/
958+
static class BeanReferring extends Referring {
959+
private final SettableBeanProperty _prop;
960+
private Object _bean;
961+
962+
public void setBean(Object bean) {
963+
_bean = bean;
964+
}
965+
966+
BeanReferring(UnresolvedForwardReference ref,
967+
Class<?> valueType, PropertyValueBuffer buffer, SettableBeanProperty prop)
968+
{
969+
super(ref, valueType);
970+
_prop = prop;
971+
}
972+
973+
@Override
974+
public void handleResolvedForwardReference(Object id, Object value) throws IOException {
975+
_prop.set(_bean, value);
976+
}
977+
}
923978
}

src/main/java/com/fasterxml/jackson/databind/deser/std/MapDeserializer.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@ public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
247247
vd = findConvertingContentDeserializer(ctxt, property, vd);
248248
}
249249
final JavaType vt = _mapType.getContentType();
250-
System.err.println("Map deser for "+_mapType+":\n vt == "+vt);
251250
if (vd == null) {
252251
vd = ctxt.findContextualValueDeserializer(vt, property);
253252
} else { // if directly assigned, probably not yet contextual, so:
@@ -668,7 +667,7 @@ public void resolveForwardReference(Object id, Object value) throws IOException
668667
* The resolved object associated with {@link #key} comes before the values in
669668
* {@link #next}.
670669
*/
671-
final static class MapReferring extends Referring {
670+
static class MapReferring extends Referring {
672671
private final MapReferringAccumulator _parent;
673672

674673
public final Map<Object, Object> next = new LinkedHashMap<Object, Object>();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.fasterxml.jackson.databind.objectid;
2+
3+
import java.util.*;
4+
5+
import com.fasterxml.jackson.annotation.*;
6+
import com.fasterxml.jackson.databind.*;
7+
8+
public class ObjectWithCreator1261Test
9+
extends BaseMapTest
10+
{
11+
static class Answer
12+
{
13+
public SortedMap<String, Parent> parents;
14+
15+
public Answer() {
16+
parents = new TreeMap<String, Parent>();
17+
}
18+
}
19+
20+
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@id")
21+
static class Parent
22+
{
23+
public Map<String, Child> children;
24+
25+
@JsonIdentityReference(alwaysAsId = true)
26+
public Child favoriteChild;
27+
28+
public String name;
29+
30+
protected Parent() { }
31+
32+
public Parent(String name, boolean ignored) {
33+
children = new TreeMap<String, Child>();
34+
this.name = name;
35+
}
36+
}
37+
38+
@JsonInclude(JsonInclude.Include.NON_NULL)
39+
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@id")
40+
static class Child
41+
{
42+
public String name;
43+
44+
@JsonIdentityReference(alwaysAsId = true)
45+
public Parent parent;
46+
47+
@JsonIdentityReference(alwaysAsId = true)
48+
public List<Parent> parentAsList;
49+
50+
public String someNullProperty;
51+
52+
protected Child() { }
53+
54+
@JsonCreator
55+
public Child(@JsonProperty("name") String name,
56+
@JsonProperty("someNullProperty") String someNullProperty) {
57+
this.name = name;
58+
this.someNullProperty = someNullProperty;
59+
}
60+
61+
public Child(String n) {
62+
name = n;
63+
}
64+
}
65+
66+
public void testObjectIds1261() throws Exception
67+
{
68+
ObjectMapper mapper = new ObjectMapper();
69+
mapper.enable(SerializationFeature.INDENT_OUTPUT);
70+
mapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
71+
72+
Answer initialAnswer = createInitialAnswer();
73+
String initialAnswerString = mapper.writeValueAsString(initialAnswer);
74+
// System.out.println("Initial answer:\n"+initialAnswerString);
75+
JsonNode tree = mapper.readTree(initialAnswerString);
76+
Answer deserializedAnswer = mapper.readValue(initialAnswerString,
77+
Answer.class);
78+
String reserializedAnswerString = mapper
79+
.writeValueAsString(deserializedAnswer);
80+
JsonNode newTree = mapper.readTree(reserializedAnswerString);
81+
if (!tree.equals(newTree)) {
82+
fail("Original and recovered Json are different. Recovered = \n"
83+
+ reserializedAnswerString + "\n");
84+
}
85+
}
86+
87+
private Answer createInitialAnswer() {
88+
Answer answer = new Answer();
89+
String child1Name = "child1";
90+
String child2Name = "child2";
91+
String parent1Name = "parent1";
92+
String parent2Name = "parent2";
93+
Parent parent1 = new Parent(parent1Name, false);
94+
answer.parents.put(parent1Name, parent1);
95+
Child child1 = new Child(child1Name);
96+
child1.parent = parent1;
97+
child1.parentAsList = Collections.singletonList(parent1);
98+
Child child2 = new Child(child2Name);
99+
Parent parent2 = new Parent(parent2Name, false);
100+
child2.parent = parent2;
101+
child2.parentAsList = Collections.singletonList(parent2);
102+
parent1.children.put(child1Name, child1);
103+
parent1.children.put(child2Name, child2);
104+
answer.parents.put(parent2Name, parent2);
105+
return answer;
106+
}
107+
}

0 commit comments

Comments
 (0)