Deserialization generic class with JsonUnwrapped

1,168 views
Skip to first unread message

Eugene Karanda

unread,
Jun 17, 2018, 11:53:49 PM6/17/18
to jackson-user
Hi there. I am working on small library that allow adding additional data to response, and I have problems with deserialization  reference class with JsonUnwrapped.
So, I have:

public class ResponseInfo {

private final int code;

private final String description;

@JsonCreator
public static ResponseInfo of(
@JsonProperty("code") int code,
@JsonProperty("description") String description
) {
return new ResponseInfo(code, description);
}

private ResponseInfo(
int code,
String description
) {
this.code = code;
this.description = description;
}

@JsonProperty("code")
public int code() {
return code;
}

@JsonProperty("description")
public String description() {
return description;
}
}

And:

@JsonDeserialize(using = ResponseDeserializer.class)
public class Response<T> {

private final ResponseInfo responseInfo;

private final T payload;

public static <T> Response<T> of(ResponseInfo responseInfo, T payload) {
return new Response<>(responseInfo, payload);
}

private Response(ResponseInfo responseInfo, T payload) {
this.responseInfo = responseInfo;
this.payload = payload;
}

@JsonUnwrapped
public ResponseInfo responseInfo() {
return responseInfo;
}

@JsonUnwrapped
public T payload() {
return payload;
}
}


For example, 

Response.of(ResponseInfo.of(0, "OK"), User.of("Oleg", 23))

will be serialized into:

{

"age": 23,

"code": 0,

"description": "OK",

"name": "Oleg"

}

 
This is test of deserialization:

public class ResponseDeserializerTest {

@Test
public void deserialization() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
final String json = "{\"code\":0,\"description\":\"OK\",\"payload\":\"Hello World\"}";

final TypeReference<Response<String>> typeReference = new TypeReference<Response<String>>() {
};

Response<String> response = objectMapper.readValue(json, typeReference);
assertThat(response)
.isEqualTo(
Response.of(
ResponseInfo.of(0, "OK"),
"Hello World"
)
);

}
}

And my implementation of JsonDeserializer

public class ResponseDeserializer
extends JsonDeserializer<Response<?>>
implements ContextualDeserializer {

private JsonDeserializer<?> payloadDeserializer;

@SuppressWarnings("unused")
public ResponseDeserializer() {
}

private ResponseDeserializer(JsonDeserializer<?> payloadDeserializer) {
this.payloadDeserializer = payloadDeserializer;
}

@Override
public JsonDeserializer<?> createContextual(
DeserializationContext context,
BeanProperty beanProperty
) throws JsonMappingException {
JavaType contextualType = context.getContextualType();

if(contextualType == null) {
contextualType = beanProperty.getMember()
.getType();
}

if (!contextualType.isTypeOrSubTypeOf(Response.class)) {
throw new IllegalArgumentException("contextualType should be " + Response.class.getName());
}

final JavaType payloadType = contextualType.containedType(0);
final JsonDeserializer<Object> payloadDeserializer
= context.findRootValueDeserializer(payloadType);

return new ResponseDeserializer(payloadDeserializer);
}

@Override
public Response<?> deserialize(
JsonParser jsonParser,
DeserializationContext context
) throws IOException, JsonProcessingException {
final ObjectCodec codec = jsonParser.getCodec();
JsonNode root = codec.readTree(jsonParser);

final ResponseInfo responseInfo = ResponseInfo.of(
root.get("code").asInt(),
root.get("description").asText()
);

final JsonNode payloadNode = root.get("payload");

final JsonParser payloadParser = payloadNode.traverse();

final Object payload = payloadDeserializer.deserialize(
payloadParser,
context
);

return Response.of(responseInfo, payload);
}
}

But I got:
java.lang.NullPointerException
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:57)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:10)
at org.overmind.br.response.ResponseDeserializer.deserialize(ResponseDeserializer.java:75)
at org.overmind.br.response.ResponseDeserializer.deserialize(ResponseDeserializer.java:17)
...

This happened cause JsonParser.getCurrentToken() returns null
How I can I convert JsonNode into Object?
 

Tatu Saloranta

unread,
Jun 18, 2018, 3:12:49 AM6/18/18
to jackson-user
Ok. First things first: when you create and use a custom deserializer,
you have to handle support for most if not all features
yourself. Annotating properties as unwrapped won't do anything automatically.
Second: your deserializer does implement ContextualDeserializer (good)
but it typically also needs to implement
and delegate `ResolvableDeserializer`.
Third: instead of getting code from parser, it is usually better to
do reads via DeserializationContext: there is `readValue()`
and `readTree()` just means you are asking for result type of
`JsonNode`. Doing that may help keep all wiring intact,
and is more efficient way (as going through parser will effectively
recreate the whole read context, lose contextual attributes
and state).

Beyond that, you may want to see what exactly is `null` for
StringDeserializer. That might help diagnose what is going on.

-+ Tatu +-
Reply all
Reply to author
Forward
0 new messages