-
-
Notifications
You must be signed in to change notification settings - Fork 143
Fix #571: Unable to deserialize a pojo with IonStruct #573
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,21 +16,51 @@ | |
|
||
import java.io.IOException; | ||
|
||
import com.amazon.ion.*; | ||
|
||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonToken; | ||
import com.fasterxml.jackson.databind.*; | ||
import com.fasterxml.jackson.databind.BeanProperty; | ||
cowtowncoder marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import com.fasterxml.jackson.databind.DeserializationContext; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.JsonMappingException; | ||
import com.fasterxml.jackson.databind.deser.ContextualDeserializer; | ||
import com.fasterxml.jackson.databind.JsonDeserializer; | ||
import com.fasterxml.jackson.databind.util.AccessPattern; | ||
import com.fasterxml.jackson.dataformat.ion.IonParser; | ||
import com.amazon.ion.IonContainer; | ||
import com.amazon.ion.IonList; | ||
import com.amazon.ion.IonSexp; | ||
import com.amazon.ion.IonStruct; | ||
import com.amazon.ion.IonSystem; | ||
import com.amazon.ion.IonType; | ||
import com.amazon.ion.IonValue; | ||
import com.amazon.ion.Timestamp; | ||
|
||
/** | ||
* Deserializer that knows how to deserialize an IonValue. | ||
*/ | ||
class IonValueDeserializer extends JsonDeserializer<IonValue> { | ||
class IonValueDeserializer extends JsonDeserializer<IonValue> implements ContextualDeserializer { | ||
|
||
private final JavaType targetType; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Member variables with leading underscore -> |
||
|
||
public IonValueDeserializer() { | ||
this.targetType = null; | ||
} | ||
|
||
public IonValueDeserializer(JavaType targetType) { | ||
this.targetType = targetType; | ||
} | ||
|
||
@Override | ||
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) { | ||
JavaType contextualType = (property != null) | ||
? property.getType() | ||
: ctxt.getContextualType(); // fallback | ||
return new IonValueDeserializer(contextualType); | ||
} | ||
|
||
@Override | ||
public IonValue deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { | ||
|
||
Object embeddedObject = jp.getEmbeddedObject(); | ||
if (embeddedObject instanceof IonValue) { | ||
return (IonValue) embeddedObject; | ||
|
@@ -62,17 +92,34 @@ public IonValue getNullValue(DeserializationContext ctxt) throws JsonMappingExce | |
if (embeddedObj instanceof IonValue) { | ||
IonValue iv = (IonValue) embeddedObj; | ||
if (iv.isNullValue()) { | ||
if (IonType.isContainer(iv.getType())) { | ||
return iv; | ||
} | ||
IonType containerType = getIonContainerType(); | ||
if (containerType != null) { | ||
IonSystem ionSystem = ((IonParser) parser).getIonSystem(); | ||
return ionSystem.newNull(containerType); | ||
} | ||
return iv; | ||
} | ||
} | ||
} | ||
|
||
cowtowncoder marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return super.getNullValue(ctxt); | ||
} catch (IOException e) { | ||
throw JsonMappingException.from(ctxt, e.toString()); | ||
} | ||
} | ||
|
||
private IonType getIonContainerType() { | ||
if (targetType != null) { | ||
Class<?> clazz = targetType.getRawClass(); | ||
if (IonStruct.class.isAssignableFrom(clazz)) return IonType.STRUCT; | ||
if (IonList.class.isAssignableFrom(clazz)) return IonType.LIST; | ||
if (IonSexp.class.isAssignableFrom(clazz)) return IonType.SEXP; | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
public AccessPattern getNullAccessPattern() { | ||
return AccessPattern.DYNAMIC; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,23 @@ | ||
package com.fasterxml.jackson.dataformat.ion.ionvalue; | ||
|
||
import java.io.IOException; | ||
import java.util.*; | ||
|
||
import com.amazon.ion.*; | ||
import com.amazon.ion.IonSystem; | ||
import com.amazon.ion.IonValue; | ||
import com.amazon.ion.IonStruct; | ||
import com.amazon.ion.system.IonSystemBuilder; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import com.fasterxml.jackson.annotation.*; | ||
import com.fasterxml.jackson.annotation.JsonAnyGetter; | ||
import com.fasterxml.jackson.annotation.JsonAnySetter; | ||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import com.fasterxml.jackson.databind.util.AccessPattern; | ||
import com.fasterxml.jackson.dataformat.ion.IonObjectMapper; | ||
import com.fasterxml.jackson.dataformat.ion.IonParser; | ||
|
||
|
||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SNAKE_CASE; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
@@ -65,7 +73,7 @@ static class IonValueData extends Data<IonValue> { | |
} | ||
|
||
private static final IonSystem SYSTEM = IonSystemBuilder.standard().build(); | ||
private static final IonValueMapper ION_VALUE_MAPPER = new IonValueMapper(SYSTEM, SNAKE_CASE); | ||
private final IonValueMapper ION_VALUE_MAPPER = new IonValueMapper(SYSTEM, SNAKE_CASE); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why change from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was changed so that it can be configured in each test, but not needed if I create a new |
||
|
||
@Test | ||
public void shouldBeAbleToDeserialize() throws Exception { | ||
|
@@ -92,23 +100,49 @@ public void shouldBeAbleToDeserializeIncludingNullList() throws Exception { | |
} | ||
|
||
@Test | ||
public void shouldBeAbleToDeserializeNullList() throws Exception { | ||
IonValue ion = ion("{c:null.list}"); | ||
|
||
IonValueData data = ION_VALUE_MAPPER.readValue(ion, IonValueData.class); | ||
public void shouldBeAbleToDeserializeNullToIonNull() throws Exception { | ||
String ion = "{c:null}"; | ||
verifyNullDeserialization(ion, SYSTEM.newNull()); | ||
ION_VALUE_MAPPER.disable(IonParser.Feature.READ_NULL_AS_IONVALUE); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should not try to change configuration of ION_VALUE_MAPPER after use, across methods; not guaranteed to take effect. Instead can create (also makes it easier to merge tests to 3.x as 3.x does not have methods to directly configure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Why is that - caching? Using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, indirectly caching of (de)serializers. Some of configuration settings affect construction and so effects remain if settings are changed. |
||
verifyNullDeserialization(ion, null); | ||
} | ||
|
||
assertEquals(1, data.getAllData().size()); | ||
assertEquals(SYSTEM.newNullList(), data.getAllData().get("c")); | ||
@Test | ||
public void shouldBeAbleToDeserializeNullList() throws Exception { | ||
String ion = "{c:null.list}"; | ||
verifyNullDeserialization(ion, SYSTEM.newNullList()); | ||
ION_VALUE_MAPPER.disable(IonParser.Feature.READ_NULL_AS_IONVALUE); | ||
verifyNullDeserialization(ion, SYSTEM.newNullList()); | ||
} | ||
|
||
@Test | ||
public void shouldBeAbleToDeserializeNullStruct() throws Exception { | ||
IonValue ion = ion("{c:null.struct}"); | ||
String ion = "{c:null.struct}"; | ||
verifyNullDeserialization(ion, SYSTEM.newNullStruct()); | ||
ION_VALUE_MAPPER.disable(IonParser.Feature.READ_NULL_AS_IONVALUE); | ||
verifyNullDeserialization(ion, SYSTEM.newNullStruct()); | ||
} | ||
|
||
IonValueData data = ION_VALUE_MAPPER.readValue(ion, IonValueData.class); | ||
@Test | ||
public void shouldBeAbleToDeserializeNullSexp() throws Exception { | ||
String ion = "{c:null.sexp}"; | ||
verifyNullDeserialization(ion, SYSTEM.newNullSexp()); | ||
ION_VALUE_MAPPER.disable(IonParser.Feature.READ_NULL_AS_IONVALUE); | ||
verifyNullDeserialization(ion, SYSTEM.newNullSexp()); | ||
} | ||
|
||
private void verifyNullDeserialization(String ionString, IonValue expected) throws Exception { | ||
|
||
IonValueData data = ION_VALUE_MAPPER.readValue(ionString, IonValueData.class); | ||
|
||
assertEquals(1, data.getAllData().size()); | ||
assertEquals(expected, data.getAllData().get("c")); | ||
|
||
IonValue ion = ion(ionString); | ||
data = ION_VALUE_MAPPER.readValue(ion, IonValueData.class); | ||
|
||
assertEquals(1, data.getAllData().size()); | ||
assertEquals(SYSTEM.newNullStruct(), data.getAllData().get("c")); | ||
assertEquals(expected, data.getAllData().get("c")); | ||
} | ||
|
||
@Test | ||
|
@@ -162,7 +196,17 @@ public void shouldBeAbleToSerializeAndDeserializeStringData() throws Exception { | |
|
||
IonValue data = ION_VALUE_MAPPER.writeValueAsIonValue(source); | ||
StringData result = ION_VALUE_MAPPER.parse(data, StringData.class); | ||
assertEquals(source, result); | ||
} | ||
|
||
@Test | ||
public void shouldBeAbleToSerializeAndDeserializeStringDataAsString() throws Exception { | ||
StringData source = new StringData(); | ||
source.put("a", "1"); | ||
source.put("b", null); | ||
|
||
String data = ION_VALUE_MAPPER.writeValueAsString(source); | ||
StringData result = ION_VALUE_MAPPER.readValue(data, StringData.class); | ||
assertEquals(source, result); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know existing is the way it is, but call above to:
looks wrong, given this is called (only) from
getEmbeddedObject()
... which should only be called when we do haveJsonToken.VALUE_EMBEDDED_OBJECT
(and if not, fail).Would probably make sense to add a comment here to explain logic wrt
!IonType.isContainer(v.getType())
-- I am not sure I understand the logic myself.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of tests fail if this
_updateToken(JsonToken.VALUE_EMBEDDED_OBJECT);
is removed. Perhaps a 3.0 cleanup activity.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. Makes sense there is a reason -- should have caught when these were added (or added comments to explain why/how)