Announcement Announcement Module
Collapse
No announcement yet.
Problem with MappingJacksonHttpMessageConverter Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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
    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();
    	}
    
    }

    Comment


    • #3
      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());
              }
          }

      Comment


      • #4
        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());

        Comment

        Working...
        X