For some reason Jackson 2.3.0 is unable to parse a JSONP response.
com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'my_json_callback':
I've got the deserialization process to work without the callback.
I've tried with the Jackson JAX-RS package which includes a @JSONP
annotation, but this seems to be used only when serializing.
Here's a trimmed-for-space version of the solution I came up with using a ReaderInterceptor
. I am using Jersey 2.x in conjunction with Jackson to interact with a web service that only outputs JSONP.
public class CallbackStripInterceptor implements ReaderInterceptor {
private final static byte[] callbackBytes = "callback(".getBytes();
@Override
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
int howMany = callbackBytes.length;
InputStream x = context.getInputStream();
if( !(x.available() >= howMany) ) {
return context.proceed();
}
x.mark( howMany );
byte[] preamble = new byte[ howMany ];
x.read( preamble );
// In case the first part of our entity doesn't have the callback String, reset the stream so downstream exploiters get the full entity.
if( !Arrays.equals( preamble, callbackBytes ) ) {
x.reset();
}
return context.proceed();
}
Use like so:
Client c = ClientBuilder.newBuilder()
.register( new CallbackStripInterceptor() )
.build();
Using this client, all responses with an entity will go through this interceptor (Jersey does not run interceptors on responses without entity bodies).
Finally I've been able to remove the callback part of the JSONP response.
First, Jackson is able to parse the JSON even when it ends with a parenthesis. So, by simply removing the my_json_callback(
from the response is enough.
Since I'm using Apache's HTTP Client, this fixes the issue:
String callback = "my_json_callback(";
InputStreamReader r = new InputStreamReader(response.getEntity().getContent());
r.skip(callback.length());
return mapper.readValue(r, MyObject.class);
The idea is not having to convert the Reader to a String and then parsing that String after removing the callback part.
I was also able to accomplish the same results using JSONTokener
from json.org
library for a given JSONP String:
JSONTokener t = new JSONTokener(json);
t.nextValue(); // skip the callback
return mapper.readValue(t.nextValue().toString(), MyObject.class);
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