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.
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
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()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With