Skip to content

Commit 0510423

Browse files
committed
Fix FasterXML#571: Unable to deserialize a pojo with IonStruct
1 parent ac24dad commit 0510423

File tree

3 files changed

+131
-22
lines changed

3 files changed

+131
-22
lines changed

ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonParser.java

+18
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ public enum Feature implements FormatFeature // in 2.12
5353
* @since 2.12.3
5454
*/
5555
USE_NATIVE_TYPE_ID(true),
56+
/**
57+
* Whether to convert "null" to an IonValueNull (true);
58+
* or leave as a java null (false) when deserializing.
59+
*<p>
60+
* Enabled by default for backwards compatibility as that has been the behavior
61+
* of `jackson-dataformat-ion` since 2.13.
62+
*
63+
* @see <a href="https://amzn.github.io/ion-docs/docs/spec.html#annot">The Ion Specification</a>
64+
*
65+
* @since 2.19.0
66+
*/
67+
READ_NULL_AS_IONVALUE(true),
5668
;
5769

5870
final boolean _defaultState;
@@ -563,6 +575,12 @@ private IonValue getIonValue() throws IOException {
563575
writer.writeValue(_reader);
564576
IonValue v = l.get(0);
565577
v.removeFromContainer();
578+
579+
if (v.isNullValue() && !Feature.READ_NULL_AS_IONVALUE.enabledIn(_formatFeatures)) {
580+
if (_valueToken == JsonToken.VALUE_NULL && !IonType.isContainer(v.getType())) {
581+
return null;
582+
}
583+
}
566584
return v;
567585
}
568586

ion/src/main/java/com/fasterxml/jackson/dataformat/ion/ionvalue/IonValueDeserializer.java

+52-5
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,51 @@
1616

1717
import java.io.IOException;
1818

19-
import com.amazon.ion.*;
20-
2119
import com.fasterxml.jackson.core.JsonParser;
2220
import com.fasterxml.jackson.core.JsonToken;
23-
import com.fasterxml.jackson.databind.*;
21+
import com.fasterxml.jackson.databind.BeanProperty;
22+
import com.fasterxml.jackson.databind.DeserializationContext;
23+
import com.fasterxml.jackson.databind.JavaType;
24+
import com.fasterxml.jackson.databind.JsonMappingException;
25+
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
26+
import com.fasterxml.jackson.databind.JsonDeserializer;
2427
import com.fasterxml.jackson.databind.util.AccessPattern;
2528
import com.fasterxml.jackson.dataformat.ion.IonParser;
29+
import com.amazon.ion.IonContainer;
30+
import com.amazon.ion.IonList;
31+
import com.amazon.ion.IonSexp;
32+
import com.amazon.ion.IonStruct;
33+
import com.amazon.ion.IonSystem;
34+
import com.amazon.ion.IonType;
35+
import com.amazon.ion.IonValue;
36+
import com.amazon.ion.Timestamp;
2637

2738
/**
2839
* Deserializer that knows how to deserialize an IonValue.
2940
*/
30-
class IonValueDeserializer extends JsonDeserializer<IonValue> {
41+
class IonValueDeserializer extends JsonDeserializer<IonValue> implements ContextualDeserializer {
42+
43+
private final JavaType targetType;
44+
45+
public IonValueDeserializer() {
46+
this.targetType = null;
47+
}
48+
49+
public IonValueDeserializer(JavaType targetType) {
50+
this.targetType = targetType;
51+
}
52+
53+
@Override
54+
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
55+
JavaType contextualType = (property != null)
56+
? property.getType()
57+
: ctxt.getContextualType(); // fallback
58+
return new IonValueDeserializer(contextualType);
59+
}
3160

3261
@Override
3362
public IonValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
63+
3464
Object embeddedObject = jp.getEmbeddedObject();
3565
if (embeddedObject instanceof IonValue) {
3666
return (IonValue) embeddedObject;
@@ -62,17 +92,34 @@ public IonValue getNullValue(DeserializationContext ctxt) throws JsonMappingExce
6292
if (embeddedObj instanceof IonValue) {
6393
IonValue iv = (IonValue) embeddedObj;
6494
if (iv.isNullValue()) {
95+
if (IonType.isContainer(iv.getType())) {
96+
return iv;
97+
}
98+
IonType containerType = getIonContainerType();
99+
if (containerType != null) {
100+
IonSystem ionSystem = ((IonParser) parser).getIonSystem();
101+
return ionSystem.newNull(containerType);
102+
}
65103
return iv;
66104
}
67105
}
68106
}
69-
70107
return super.getNullValue(ctxt);
71108
} catch (IOException e) {
72109
throw JsonMappingException.from(ctxt, e.toString());
73110
}
74111
}
75112

113+
private IonType getIonContainerType() {
114+
if (targetType != null) {
115+
Class<?> clazz = targetType.getRawClass();
116+
if (IonStruct.class.isAssignableFrom(clazz)) return IonType.STRUCT;
117+
if (IonList.class.isAssignableFrom(clazz)) return IonType.LIST;
118+
if (IonSexp.class.isAssignableFrom(clazz)) return IonType.SEXP;
119+
}
120+
return null;
121+
}
122+
76123
@Override
77124
public AccessPattern getNullAccessPattern() {
78125
return AccessPattern.DYNAMIC;

ion/src/test/java/com/fasterxml/jackson/dataformat/ion/ionvalue/IonValueDeserializerTest.java

+61-17
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package com.fasterxml.jackson.dataformat.ion.ionvalue;
22

3-
import java.io.IOException;
4-
import java.util.*;
5-
6-
import com.amazon.ion.*;
3+
import com.amazon.ion.IonSystem;
4+
import com.amazon.ion.IonValue;
5+
import com.amazon.ion.IonStruct;
76
import com.amazon.ion.system.IonSystemBuilder;
8-
import org.junit.jupiter.api.Test;
9-
10-
import com.fasterxml.jackson.annotation.*;
7+
import com.fasterxml.jackson.annotation.JsonAnyGetter;
8+
import com.fasterxml.jackson.annotation.JsonAnySetter;
9+
import com.fasterxml.jackson.annotation.JsonProperty;
1110
import com.fasterxml.jackson.databind.util.AccessPattern;
1211
import com.fasterxml.jackson.dataformat.ion.IonObjectMapper;
12+
import com.fasterxml.jackson.dataformat.ion.IonParser;
13+
14+
15+
import java.io.IOException;
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
import java.util.Objects;
19+
20+
import org.junit.jupiter.api.Test;
1321

1422
import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SNAKE_CASE;
1523
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -65,7 +73,7 @@ static class IonValueData extends Data<IonValue> {
6573
}
6674

6775
private static final IonSystem SYSTEM = IonSystemBuilder.standard().build();
68-
private static final IonValueMapper ION_VALUE_MAPPER = new IonValueMapper(SYSTEM, SNAKE_CASE);
76+
private final IonValueMapper ION_VALUE_MAPPER = new IonValueMapper(SYSTEM, SNAKE_CASE);
6977

7078
@Test
7179
public void shouldBeAbleToDeserialize() throws Exception {
@@ -92,23 +100,49 @@ public void shouldBeAbleToDeserializeIncludingNullList() throws Exception {
92100
}
93101

94102
@Test
95-
public void shouldBeAbleToDeserializeNullList() throws Exception {
96-
IonValue ion = ion("{c:null.list}");
97-
98-
IonValueData data = ION_VALUE_MAPPER.readValue(ion, IonValueData.class);
103+
public void shouldBeAbleToDeserializeNullToIonNull() throws Exception {
104+
String ion = "{c:null}";
105+
verifyNullDeserialization(ion, SYSTEM.newNull());
106+
ION_VALUE_MAPPER.disable(IonParser.Feature.READ_NULL_AS_IONVALUE);
107+
verifyNullDeserialization(ion, null);
108+
}
99109

100-
assertEquals(1, data.getAllData().size());
101-
assertEquals(SYSTEM.newNullList(), data.getAllData().get("c"));
110+
@Test
111+
public void shouldBeAbleToDeserializeNullList() throws Exception {
112+
String ion = "{c:null.list}";
113+
verifyNullDeserialization(ion, SYSTEM.newNullList());
114+
ION_VALUE_MAPPER.disable(IonParser.Feature.READ_NULL_AS_IONVALUE);
115+
verifyNullDeserialization(ion, SYSTEM.newNullList());
102116
}
103117

104118
@Test
105119
public void shouldBeAbleToDeserializeNullStruct() throws Exception {
106-
IonValue ion = ion("{c:null.struct}");
120+
String ion = "{c:null.struct}";
121+
verifyNullDeserialization(ion, SYSTEM.newNullStruct());
122+
ION_VALUE_MAPPER.disable(IonParser.Feature.READ_NULL_AS_IONVALUE);
123+
verifyNullDeserialization(ion, SYSTEM.newNullStruct());
124+
}
107125

108-
IonValueData data = ION_VALUE_MAPPER.readValue(ion, IonValueData.class);
126+
@Test
127+
public void shouldBeAbleToDeserializeNullSexp() throws Exception {
128+
String ion = "{c:null.sexp}";
129+
verifyNullDeserialization(ion, SYSTEM.newNullSexp());
130+
ION_VALUE_MAPPER.disable(IonParser.Feature.READ_NULL_AS_IONVALUE);
131+
verifyNullDeserialization(ion, SYSTEM.newNullSexp());
132+
}
133+
134+
private void verifyNullDeserialization(String ionString, IonValue expected) throws Exception {
135+
136+
IonValueData data = ION_VALUE_MAPPER.readValue(ionString, IonValueData.class);
137+
138+
assertEquals(1, data.getAllData().size());
139+
assertEquals(expected, data.getAllData().get("c"));
140+
141+
IonValue ion = ion(ionString);
142+
data = ION_VALUE_MAPPER.readValue(ion, IonValueData.class);
109143

110144
assertEquals(1, data.getAllData().size());
111-
assertEquals(SYSTEM.newNullStruct(), data.getAllData().get("c"));
145+
assertEquals(expected, data.getAllData().get("c"));
112146
}
113147

114148
@Test
@@ -162,7 +196,17 @@ public void shouldBeAbleToSerializeAndDeserializeStringData() throws Exception {
162196

163197
IonValue data = ION_VALUE_MAPPER.writeValueAsIonValue(source);
164198
StringData result = ION_VALUE_MAPPER.parse(data, StringData.class);
199+
assertEquals(source, result);
200+
}
201+
202+
@Test
203+
public void shouldBeAbleToSerializeAndDeserializeStringDataAsString() throws Exception {
204+
StringData source = new StringData();
205+
source.put("a", "1");
206+
source.put("b", null);
165207

208+
String data = ION_VALUE_MAPPER.writeValueAsString(source);
209+
StringData result = ION_VALUE_MAPPER.readValue(data, StringData.class);
166210
assertEquals(source, result);
167211
}
168212

0 commit comments

Comments
 (0)