Search code examples
jsonresteasywildfly-8

Resteasy PUT or POST with Json Object causes UnrecognizedPropertyException


This is about a REST PUT call with a JSON Object whith a root element, let's say, something like :

{"place":
    {"name":"Here",
     "address":
         {"address":"somewhere under the rainbow",
          "city":
                 {"name":"Oz",
                  "country":
                    {"name":"Oz",
                     "zone":"Far Far Away"}
                  }
          }
     } 
}

defined with Jaxb Annotations

@XmlRootElement
public class PlaceOffline extends GlobalElement {
     @XmlElement
     public Address address;
}

and the superclass is

@XmlRootElement
public class GlobalElement implements Serializable {
     @XmlElement
     public Integer id;
     @XmlElement
     public String name;
     @XmlElement
     public String uniqueLabel;
}

I wrote a client and It miserabily failed with an

 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "placeOffline"

I decide then to log the call and create a ContainerRequestFilter to do this

@Provider
public class RestLogFilter implements ContainerRequestFilter {
    private Logger LOGGER_filter = Logger.getLogger(RestLogFilter.class.getName());

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        if (!MediaType.APPLICATION_JSON_TYPE.equals(requestContext.getMediaType())
            || requestContext.getEntityStream() == null) {
            return;
        }
    InputStream inputStream = requestContext.getEntityStream();
    byte[] bytes = IOUtils.readFully(inputStream , -1, true);
    LOGGER_filter.info("Posted: " + new String(bytes, "UTF-8"));
    requestContext.getEntityStream().mark(0);
}

}

And, guess what ?

It worked, unexpectedly ! No more Errors. The printing of the entity solves the problem. Awkward.

I see it like a workaround and it bothers me a little, has anyone an explanation ? A more elegant way to solve this issue ?

I am using WildFly 8.2

EDIT :

It worked with

public class ObjectMapperContextResolver implements     ContextResolver<ObjectMapper> {
    private final ObjectMapper mapper;

    public ObjectMapperContextResolver() {
        mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return mapper;
    }
}

Solution

  • You need to add Jackson to the application (just to compile), as you will need some of it's classes. Wildfly uses Jackson to deserialize JSON, and the default behavior is to fail when it sees properties on the JSON that are not modeled in the POJO. For example the following JSON

    {"firstName": "stack", "lastName": "overflow"}
    

    has a property "lastName" which is not in the following POJO

    public class Person {
        private String firstName;
        public void setFirstName(String firstName) { this.firstName = firstName; }
        public String getFirstName() { return firstName; }
    }
    

    So it would fail with Jackson's default behavior.

    For the dependency, you can add

    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jackson2-provider</artifactId>
        <version>3.0.9.Final</version>
        <scope>provided</scope>
    </dependency>
    

    Notice the provided scope, as Wildfly already has this dependency. If you are not using Maven, really all you need is jackson-databind and jacskon-annotations. Just grab the 2.4.x version. If you are using Jars, it iis important that these jars do not get packaged with your war, as the version may conflict with what Wildfly already has.

    Now if you want to just ignore unknown properties for this one class, you can just us an annotation for the class

    @JsonIgnoreProperties(ignoreUnknown = true)
    public class PlaceOffline extends GlobalElement {
    

    Or if you want to configure this behavior globally, you can see this answer. How to configure the ObjectMapper is by the following

    ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    

    UPDATE

    If the failure is due to the fact the the JSON is being wrapped, for example

    {"person": {"firstName": "stack", "lastName": "overflow"}}
    

    Then you need to set the property

    mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
    

    What this does is look for the value of the class name in either @XmlRootElement(name="person") or @JsonRootName("person") annotated on the class. For the JAXB annotation support, you will still need to configure the JAXB annotation module

    mapper.registerModule(new JaxbAnnotationModule());
    

    This module is contained in the jackson-module-jaxb-annotations jar. It is already pulled in if you are using Maven (with the above dependency).