15

I'm trying to use the Jackson json parser(v2.5.2) to parse a custom json document that isn't true json and I can't figure out how to make it work. I have a json document that might look like:

{
    "test": {
        "one":"oneThing",
        "two": nonStandardThing(),
        "three": true
    }
}

I want to use the ObjectMapper to map this to a java.util.Map and I would just like the nonStandardThing() to be added as a String value in my map for the key two.

When I run this through the ObjectMapper.readValue(json, Map.class) I get the exception:

com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'nonStandardThing': was expecting 'null', 'true', 'false' or NaN
 at [Source: { "test":{"test1":nonStandardThing(),"test2":"two"}}; line: 1, column: 35]
    at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1487)
    at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:518)
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._reportInvalidToken(ReaderBasedJsonParser.java:2300)
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._reportInvalidToken(ReaderBasedJsonParser.java:2277)

I have tried to register a DeserializationProblemHandler with the ObjectMapper but it is never called when this problem occurs.

Here is sample application that shows what I have tried:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JacksonDeserializerTest {
    private Logger log = Logger.getLogger(JacksonDeserializerTest.class.getName());
    public JacksonDeserializerTest() {
        String validJson = "{ \"test\":{\"test1\":\"one\",\"test2\":\"two\"}}";
        String invalidJson = "{ \"test\":{\"test1\":nonStandardThing(),\"test2\":\"two\"}}";

        ObjectMapper mapper = new ObjectMapper();
        mapper.addHandler(new DeserializationProblemHandler() {
            @Override
            public boolean handleUnknownProperty(DeserializationContext dc, JsonParser jp, JsonDeserializer<?> jd, Object bean, String property) throws IOException, JsonProcessingException {
                System.out.println("Handling unknown property: " + property);
                return false;
            }
        });

        try {
            log.log(Level.INFO, "Valid json looks like: {0}", mapper.readValue( validJson, Map.class).toString());
            log.log(Level.INFO, "Invalid json looks like: {0}", mapper.readValue(invalidJson, Map.class).toString());
        } catch (IOException ex) {
            log.log(Level.SEVERE, "Error parsing json", ex);
        }

    }

    public static void main(String[] args) {
        JacksonDeserializerTest test = new JacksonDeserializerTest();
    }
}

The output looks like:

Apr 24, 2015 1:40:27 PM net.acesinc.data.json.generator.jackson.JacksonDeserializerTest <init>
INFO: Valid json looks like: {test={test1=one, test2=two}}
Apr 24, 2015 1:40:27 PM net.acesinc.data.json.generator.jackson.JacksonDeserializerTest <init>
SEVERE: Error parsing json
com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'nonStandardThing': was expecting 'null', 'true', 'false' or NaN
 at [Source: { "test":{"test1":nonStandardThing(),"test2":"two"}}; line: 1, column: 35]
    at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1487)
    at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:518)
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._reportInvalidToken(ReaderBasedJsonParser.java:2300)
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._reportInvalidToken(ReaderBasedJsonParser.java:2277)
    at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._matchToken(ReaderBasedJsonParser.java:2129)

Can anyone point out why the Handler never gets called? Or, if there is a better parse this custom json document (jackson or not...), let me know.

Andrew Serff
  • 2,117
  • 4
  • 21
  • 32
  • For anyone that still needs solution, there is another way to custom Jackson's behavior is customized JsonParser. See jackson's source code of JsonFactory, ReaderBasedJsonParser#nextToken(); see [this](https://stackoverflow.com/a/67052210/5142886) – tianzhipeng Apr 12 '21 at 03:59

2 Answers2

11

The handler is not called because the invalid part is not the property ("two") but the value (nonStandardThing()).

An obvious way to handle this, is to pass nonStandardThing() as a String, i.e. rewrite the JSON document as

{
    "test": {
        "one":"oneThing",
        "two": "nonStandardThing()",
        "three": true
    }
}

If that is not a possibility, there is not much to do. Using a custom Jackson Deserializer is only useful for properties, not values.

PNS
  • 19,295
  • 32
  • 96
  • 143
  • And by `Jackson Deserializer` you mean a custom java.util.Map Deserializer? That's what I was worried about... – Andrew Serff Apr 24 '15 at 20:38
  • 1
    Not even that will provide a solution. From discussions in the `Jackson` mailing list, it is clear that the necessary functionality currently exists for properties, not values. Presumably you have no control on the serialization process? +1 for an interesting question anyway. :-) – PNS Apr 24 '15 at 22:03
  • Well, I actually might be able to change this format, but it interesting that `Jackson` doesn't allow you to process values. Bummer, cause it could happen again. I suppose you could preprocess the json to quote the non-standard values and make it valid. In any case, your answer (which had crossed my mind previously) got me thinking about the problem differently again and now I'm reworking things to fit this better. Thanks! – Andrew Serff Apr 26 '15 at 04:04
4

Content you list is unfortunately not valid JSON, so what you have is not really a JSON document, but perhaps serialization of a Javascript object. All String values MUST be enclosed in double quotes in JSON.

Jackson does not support reading of such content directly, but it may be possible to read this using YAML parser like SnakeYAML. Jackson also has YAML data format module at https://github.com/FasterXML/jackson-dataformat-yaml/ so you could perhaps use that. Given that YAML is (mostly!) a superset of JSON, it could probably do what you want.

StaxMan
  • 113,358
  • 34
  • 211
  • 239