How to detect trailing garbage using Jackson Objec

2019-02-24 18:45发布

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.

3条回答
ゆ 、 Hurt°
2楼-- · 2019-02-24 19:21

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().

查看更多
Emotional °昔
3楼-- · 2019-02-24 19:23

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

查看更多
乱世女痞
4楼-- · 2019-02-24 19:32

You can have something like below. Basically loop through the tokens before you readValue. JsonParser#nextToken() validates if your String is a proper JSON.

Here is the code:

public static void main(String[] args) throws JsonParseException, IOException  {

        ObjectMapper mapper = new ObjectMapper();

        String str = "{\"x\" : 3} garbage";

        JsonParser x = mapper.getJsonFactory().createJsonParser(str);
        while(x.nextToken()!=null){

        }
    A a =   mapper.readValue(str, A.class);

        System.out.println(a);
    }

console output:

Exception in thread "main" org.codehaus.jackson.JsonParseException: Unexpected character ('g' (code 103)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
 at [Source: java.io.StringReader@65faba46; line: 1, column: 12]
    at org.codehaus.jackson.JsonParser._constructError(JsonParser.java:1432)
    at org.codehaus.jackson.impl.JsonParserMinimalBase._reportError(JsonParserMinimalBase.java:385)
    at org.codehaus.jackson.impl.JsonParserMinimalBase._reportUnexpectedChar(JsonParserMinimalBase.java:306)
    at org.codehaus.jackson.impl.ReaderBasedParser._handleUnexpectedValue(ReaderBasedParser.java:1192)
    at org.codehaus.jackson.impl.ReaderBasedParser.nextToken(ReaderBasedParser.java:479)
    at example.JsonParse.main(JsonParse.java:21)
查看更多
登录 后发表回答