3

I'm trying to extend the Jackson default deserialization to assign a placeholder object to the fields of my domain object when they are set to null explicitly in the json. I'm using Spring Boot + Spring Data. I searched a lot for the best way to do it and I believe a custom deserializer is what I want. I'm open for suggestions about that too, but my actual question is about how to use the default deserialization and handle the explicit null assignments myself. This is where I'm stuck (in MyItemDeserializer):

@Override
public Item deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
        throws IOException, JsonProcessingException
{
    Item item = (Item) defaultDeserializer.deserialize(jsonParser, deserializationContext);
    JsonNode root = jsonParser.readValueAsTree();
    // traverse the tree to handle {"something":null}
    return item;
}

Obviously after the default deserializer processed the jsonParser object readValueAsTree returns null. Is there a way to have the json as a tree (or anything else) after the default deserialization?

Stefan Berger
  • 195
  • 14

1 Answers1

7

If I understand your question correctly, what you would want to do is kind of reversal of code you have: instead of passing JsonParser as is, then trying to re-read, you should FIRST read contents, create JsonParser out of them, and re-use those contents.

One way is to read contents as JsonNode, and construct parser using com.fasterxml.jackson.databind.node.TreeTraversingParser:

JsonParser p2 = new TreeTraversingParser(node);
Item item = (Item) defaultDeserializer.deserialize(p2, deserializationContext);

Another possibility is to read contents as TokenBuffer, and construct 2 parsers out of it: this is what internal code uses for buffering. This would work something like:

TokenBuffer buf = new TokenBuffer(jsonParser);
b.copyCurrentStructure(jsonParser);
JsonParser p2 = buf.asParser(); // for default deserializer
JsonParser p3 = buf.asParser(); // for further processing, read as tree, whatever

although building a tree is bit more cumbersome in this case.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • 2
    Didn't know TreeTraversingParser. Thanks! – Stefan Berger Jan 13 '17 at 12:22
  • It is bit hidden, and it might make sense to expose it via a method in `JsonNode` or something ("asParser()" or such). – StaxMan Jan 13 '17 at 19:16
  • The TreeTraversingParser approach worked for me too. The other one didn't (tokenBuffer). The new parsers created from the tokenbuffer didn't contain the objects from the first parser. – Julio Feb 22 '19 at 14:39
  • Neither of these are working for me. I just receive unexpected end-of-input when I try to use any of the above solutions. How would you get the node for the TreeTraversingParser constructor? It seems getting the node out of the original parser already sets that parser to the end of its stream, so any copy of it useless after this point. – Balázs Nemes Mar 14 '19 at 15:10
  • 3
    No, `Node` has no concept of stream, it is completely decoupled from its source (parser, or explicitly built). Node does, however, have structure to traverse, either explicitly or constructing separate `TreeTraversingParser`. One caveat is that when creating parser, it initially does not point to anything so you must call `nextToken()` or equivalent. – StaxMan Mar 14 '19 at 19:37
  • @StaxMan Yep, a call of `nexToken()` really made the wonder. Thank you! – Balázs Nemes Mar 19 '19 at 07:56
  • @BalázsNemes great! It is bit of an undocumented part and not necessarily obvious -- much of codebase handles such case gracefully (checks if parser does point to a token), but not everything, and custom code has to be aware of it. – StaxMan Mar 19 '19 at 20:01
  • 1
    A better approach to the last solution could be the following: `val tokenBuffer = context.bufferAsCopyOfValue(parser); val jsonNode = context.readTree(tokenBuffer.asParserOnFirstToken())` – gscaparrotti Oct 21 '22 at 08:02