3

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.

Boyolame
  • 329
  • 2
  • 13
  • You have the problem that you rely on object member order which the RFC for JSON (RFC 7159) says is irrelevant. Why do you want the order? Why don't you just try and deserialize both? – fge Feb 15 '15 at 11:06
  • @fge Because a specific field has an information about how should I parse other fields. Parsing the JSON tree to a generic JsonNode is always an option but it comes with a overhead so I wanted to avoid it. – Boyolame Feb 15 '15 at 20:59
  • @fge, since you know about Avro, what I wanted to do is to parse the JSON to an Avro model. Some of the fields in JSON has information about the Avro schema so in order to parse the tree, first I need to know those values. You can look the code from here: https://github.com/buremba/rakam/blob/19d96f7ff35df81b4faa4a57452520f905122c74/src/main/java/org/rakam/collection/event/EventDeserializer.java – Boyolame Feb 15 '15 at 21:03

2 Answers2

3

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.

Boyolame
  • 329
  • 2
  • 13
2

A custom deserializer needs uses the streaming API. There is no way to go back, reparse, etc.

Did you register the custom deserializer for the field type or for the class, that contains the fields, that need this special treatment?

If you register the deserializer for the class that contains those fields, you can just use the streaming API, read in all the fields of an instance, store them temporarily, e.g. in a HashMap, and then assign the values.

BTW: Your question smells like the XYProblem. Maybe you should post another question about the reason, you need to solve this problem here and see whether there is a better approach for it.

cruftex
  • 5,545
  • 2
  • 20
  • 36
  • I found a way to do it using custom JsonParser. Unfortunately HashMap comes with an overhead and as I said performance is critical for this part of the application. – Boyolame Feb 15 '15 at 20:55