Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect trailing garbage using Jackson ObjectMapper

Say I have a class

class A {
    public int x;
}

Then a valid json can be parsed as follows:

ObjectMapper mapper = new ObjectMapper();
A a = mapper.readValue("{\"x\" : 3}", A.class);

Is there a way to have the parser fail if the string contains more data than necessary to parse the object?

For example I would like the following to fail (which succeeds)

A a = mapper.readValue("{\"x\" : 3} trailing garbage", A.class);

I tried it using an InputStream with JsonParser.Feature.AUTO_CLOSE_SOURCE=false and checking whether the stream has been consumed completely, but that does not work:

A read(String s) throws JsonParseException, JsonMappingException, IOException {
    JsonFactory f = new MappingJsonFactory();
    f.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
    ObjectMapper mapper = new ObjectMapper(f);
    InputStream is = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8));
    try {
        A a = mapper.readValue(is, A.class);
        if(is.available() > 0) {
            throw new RuntimeException();
        }
        return a;
    } finally {
        is.close();
    }
}

that is,

read("{\"x\" : 3} trailing garbage");

still succeeds, probably because the parser consumes more from the stream than strictly necessary.

One solution that works is to verify that the parsing fails when dropping the last charachter from the string:

A read(String s) throws JsonParseException, JsonMappingException, IOException {
    ObjectMapper mapper = new ObjectMapper();
    A a = mapper.readValue(s, A.class);

    if (s.length() > 0) {
        try {
            mapper.readValue(s.substring(0, s.length()-1), A.class);
            throw new RuntimeException();
        } catch (JsonParseException e) {
        }
    }

    return a;
}

but I'm looking for a more efficient solution.

like image 995
veryltdbeard Avatar asked Dec 08 '22 06:12

veryltdbeard


2 Answers

As of Jackson version 2.9, there is now DeserializationFeature.FAIL_ON_TRAILING_TOKENS which can be used to achieve that:

ObjectMapper objectMapper =
        new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);

See https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.9 and https://medium.com/@cowtowncoder/jackson-2-9-features-b2a19029e9ff

like image 134
anre Avatar answered Dec 11 '22 08:12

anre


The main thing to do is to create a JsonParser first, separately, then call ObjectMapper.readValue() passing that parser, and THEN call nextToken() once more and verify it returns null (instead of non-null value, or throw exception).

So, something like

JsonParser jp = mapper.getFactory().createParser(jsonSource);
try {
    Value v = mapper.readValue(jp, Value.class);
    if (jp.nextToken() != null) {
        //throw some exception: trailing garbage detected
    }
    return v;
} finally {
    jp.close();
}

Note: This is for Jackson 2.x. For Jackson 1.x, use getJsonFactory().createJsonParser() instead of getFactory().createParser().

like image 20
StaxMan Avatar answered Dec 11 '22 09:12

StaxMan