Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserialize JSONP with Jackson

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.

like image 572
Eldelshell Avatar asked Sep 15 '25 07:09

Eldelshell


2 Answers

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

like image 101
Ryon Day Avatar answered Sep 17 '25 21:09

Ryon Day


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);
like image 44
Eldelshell Avatar answered Sep 17 '25 20:09

Eldelshell