Results 1 to 4 of 4

Thread: Problem with MappingJacksonHttpMessageConverter

  1. #1

    Default Problem with MappingJacksonHttpMessageConverter

    Hi
    I'm adding support for new service provider and got the following problem.

    Each returned type is wrapped by the same json object, e.g. person would be returned in the following way:
    Code:
    {
      "entry" : {
        "id:"person.id",
        "name":"Bob"
      }
    }
    and photo
    Code:
    {
      "entry":{
        "id":"photo.id",
        "uri":"http://photo.uri"
      }
    }
    I do not want to implement for each type separate wrapper (JsonObjPerson, JsonObjPhoto, etc), but would like to have sth like
    Code:
    class JsonObj<T> {
      T entry;
    }
    In Jackons to deserialize such object I would do the following
    Code:
    objectMapper.readValue(json, new TypeReference<JsonObject<Person>>()
    , but MappingJacksonHttpMessageConverter (and HttpMessageConverter in general) require Class object and I'm unable to pass information about internal object.

    Any hint how to bypass it is really appreciated.

  2. #2

    Default

    Ok, found some nasty solution, but will share it anyway. Maybe someone can tweak it.

    Step 1 - extend MappingJacksonHttpMessageConverter and overwrite canRead and readInternal methods, so that converter will be able to make use of TypeReference
    Step 2 - in NkTemplate class overwrite json message converter
    Step 3 - create proper instance of TypeReference

    Code:
    public class TypeReferenceJacksonMessageConverter extends MappingJacksonHttpMessageConverter {
    
    	@Override
    	public boolean canRead(Class<?> clazz, MediaType mediaType) {
    		
    		if (TypeReference.class.isAssignableFrom(clazz)) {
    			// if Class<TypeReference<X>> passed, then check X
    	        Type pt = ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0];
    			return super.canRead(pt.getClass(), mediaType);
    		}
    		return super.canRead(clazz, mediaType);
    	}
    
    	@Override
    	protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    		
    		try {
    			if (TypeReference.class.isAssignableFrom(clazz)) {
    				Constructor<?> c = clazz.getDeclaredConstructors()[0];
    				c.setAccessible(true);
    				@SuppressWarnings("rawtypes")
    				TypeReference ref = (TypeReference) c.newInstance(new Object[]{});
    				return this.getObjectMapper().readValue(inputMessage.getBody(), ref);
    			}
    			return this.getObjectMapper().readValue(inputMessage.getBody(), clazz);
    		} catch (JsonProcessingException ex) {
    			throw new HttpMessageNotReadableException("Could not read JSON: ", ex);
    		} catch (InvocationTargetException ite) {
    			throw new HttpMessageNotReadableException("Could not create instance of TypeReference: ", ite);
    		} catch (IllegalAccessException iae) {
    			throw new HttpMessageNotReadableException("Could not create instance of TypeReference: ", iae);
    		} catch (InstantiationException e) {
    			throw new HttpMessageNotReadableException("Could not create instance of TypeReference:  ", e);
    		} 
    	}
    }
    Code:
    public class NkTemplate extends AbstractOAuth2ApiBinding implements Nk {
    
    	
    ...	
    	@Override
    	protected MappingJacksonHttpMessageConverter getJsonMessageConverter() {
    		return new TypeReferenceJacksonMessageConverter();
    	}
    	
    
    }

    Code:
    public class FetchNkProfile {
    
    	
    	private static final TypeReference<JsonObject<NkProfile>> typeReference = new TypeReference<JsonObject<NkProfile>>() {
    	};
    	
            public NkProfile getUserProfile() {
    		Object jsonObject = getRestTemplate().getForObject(buildUri("/people/@me"), typeReference.getClass());
    		return ((JsonObject<NkProfile>)jsonObject).getEntry();
    	}
    
    }

  3. #3
    Join Date
    Nov 2008
    Posts
    24

    Default

    I had a similar issue working with the Tumblr API. I wound up creating a class that represents a generic response from their API. It just has a String field for the JSON for the more specific response objects:

    TumblrResponse.java

    Then I used a customized subclass of MappingJacksonHttpMessageConverter too, like you did:

    TumblrHttpMessageConverter.java

    In the message converter I'm basically splitting the deserialization into two steps, one to deserialize the response, then if the response is not an error response, another to deserialize the more specific response object. Here is the relevant code:

    Code:
        @Override
        protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
            try {
                TumblrResponse tumblrResponse = objectMapper.readValue(inputMessage.getBody(), TumblrResponse.class);
                checkResponse(tumblrResponse);
                Object result;
                if (clazz.equals(TumblrResponse.class)) {
                    // don't parse the response json, callee is going to process it manually
                    result = tumblrResponse;
                } else {
                    // parse the response json into an instance of the given class
                    result = objectMapper.readValue(tumblrResponse.getResponseJson(), clazz);
                }
                return result;
            }
            catch (JsonParseException ex) {
                throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
            }
            catch (EOFException ex) {
                throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
            }
        }
    ...
        protected void checkResponse(TumblrResponse tumblrResponse) {
            HttpStatus httpStatus = HttpStatus.valueOf(tumblrResponse.getStatus());
            if (httpStatus.series() == HttpStatus.Series.CLIENT_ERROR) {
                throw new HttpClientErrorException(httpStatus, tumblrResponse.getMessage());
            } else if (httpStatus.series() == HttpStatus.Series.SERVER_ERROR) {
                throw new HttpServerErrorException(httpStatus, tumblrResponse.getMessage());
            }
        }

  4. #4

    Default

    Without any details it may be difficult to help you, but you can take a look at the NkTemplate . In constructor there is a line, that sets message converters.
    Code:
    getRestTemplate().setMessageConverters(getMessageConverters());

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •