Search code examples
javajsonjacksondeserializationjackson-modules

Reading skipped children in Jackson Custom Deserializer


I have a deserializer for a specific class which needs some ordering while reading fields. Let's say I have two fields in my class (field1 and field2) and in order to read field2, it first needs field1.

For example for the following json data it works because when the deserializer parses field2, field1 is already set:

{"field1": 3, "field2": 4}

However if we reverse the fields:

{"field2": 4, "field1": 3}

I need to skip field2 via jp.skipChildren because field1 is not set. When field1 is parsed, Jackson should re-read and parse field2.

One option is to parse field2 instead of skipping and hold it in a variable so that when field1 is set, it can use the variable that holds data in field2. However; based on the value of field1, I may not need to parse field2 so I'm looking for a better solution since performance is critical in this part of the code.

I'm using Mapper.readValue(byte[], MyClass.class) method and it seems Jackson uses ReaderBasedJsonParser for parsing. Even though it's possible to get token position, I couldn't find a way to set token position.


Solution

  • Finally I found a way to do it. It's actually a workaround but it passes the tests that I wrote. When you pass byte array to mapper.readValue it uses ReaderBasedJsonParser which iterates through the array and parse the JSON tree.

    public static class SaveableReaderBasedJsonParser extends ReaderBasedJsonParser {
        private int savedInputPtr = -1;
    
        public SaveableReaderBasedJsonParser(IOContext ctxt, int features, Reader r, ObjectCodec codec, CharsToNameCanonicalizer st, char[] inputBuffer, int start, int end, boolean bufferRecyclable) {
            super(ctxt, features, r, codec, st, inputBuffer, start, end, bufferRecyclable);
        }
    
        public void save() {
            savedInputPtr = _inputPtr;
        }
    
        public boolean isSaved() {
            return savedInputPtr>-1;
        }
    
        public void load() {
            _currToken = JsonToken.START_OBJECT;
            _inputPtr = savedInputPtr;
            _parsingContext = _parsingContext.createChildObjectContext(0, 0);
        }
    }
    

    When you use this JsonParser, the JsonParser instance that will be passed to your deserializer EventDeserializer.deserialize(JsonParser, DeserializationContext) will be a SaveableReaderBasedJsonParser so you can safely cast it.

    When you want to save the position, call jp.save() so that when you need to go back, you can call just call jp.load().

    As I said, it's actually a workaround but when you need this kind of feature and don't want to parse the tree twice for performance reasons, you may give it a try.