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.
The main thing to do is to create a
JsonParser
first, separately, then callObjectMapper.readValue()
passing that parser, and THEN callnextToken()
once more and verify it returns null (instead of non-null value, or throw exception).So, something like
Note: This is for Jackson 2.x. For Jackson 1.x, use
getJsonFactory().createJsonParser()
instead ofgetFactory().createParser()
.As of Jackson version 2.9, there is now DeserializationFeature.FAIL_ON_TRAILING_TOKENS which can be used to achieve that:
See https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.9 and https://medium.com/@cowtowncoder/jackson-2-9-features-b2a19029e9ff
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:
console output: