Diver
Diver

Reputation: 1608

What is the moxy equivalent to Jackson's JsonAnySetter?

I'm attempting to move to Jersey 2.0. Which is giving me pains with Jackson, and the docs recommend using Moxy.

I got Moxy working for get and post calls where everything matches nicely however I have a need to deal with possible unknown elements.

// Handle unknown deserialization parameters
@JsonAnySetter
protected void handleUnknown(String key, Object value) {
    if (unknownParameters == null) {
        unknownParameters = new HashMap<>();
    }
    unknownParameters.put(key, value);
}

This worked well with prior to changing to jersey 2.0 and even though it doesn't cause any problems when I leave it in it never gets called.

How do I implement this in Jersey 2.0? I'm fine with Moxy or Jackson.

my dependencies

'org.codehaus.jackson:jackson-mapper-asl:1.9.2',
'org.eclipse.persistence:org.eclipse.persistence.moxy:2.5.0',
'org.glassfish.jersey.media:jersey-media-moxy:2.0',

This has no effect in my web.xml

<init-param>
    <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
    <param-value>true</param-value>
</init-param>

Upvotes: 3

Views: 2980

Answers (3)

bdoughan
bdoughan

Reputation: 149037

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

MOXy does not have a direct equivalent to Jackson's @JsonAnySetter. I have entered the following enhancement request to add this type of behaviour:

Below are some information on some MOXy's extensions that may apply to your use case.


MOXy's @XmlVirtualAccessMethods

If the JSON properties aren't really unknown, they just don't exist as properties on your domain model then you can use MOXy's @XmlVirtualAccessMethods extension (see: http://blog.bdoughan.com/2011/06/extensible-models-with-eclipselink-jaxb.html).

Java Model

Customer

The @XmlVirtualAccess methods annotation is used to specify that the Customer class is extensible.

import java.util.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlVirtualAccessMethods;

@XmlVirtualAccessMethods(setMethod = "put")
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    private String firstName;
    private Address billingAddress;

    @XmlTransient
    private Map<String, Object> extensions = new HashMap<String, Object>();

    public <T> T get(String property) {
        return (T) extensions.get(property);
    }

    public void put(String property, Object value) {
        extensions.put(property, value);
    }

}

Address

package forum18068176;

public class Address {

    private String street;

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

}

Mapping Document (oxm.xml)

The definitions for the extension properties are defined in MOXy's mapping document (see: http://blog.bdoughan.com/2011/04/moxys-xml-metadata-in-jax-rs-service.html).

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum18068176">
    <java-types>
        <java-type name="Customer">
            <xml-type prop-order="firstName lastName billingAddress shippingAddress"/>
            <java-attributes>
                <xml-element
                    java-attribute="lastName"
                    type="java.lang.String"/>
                <xml-element
                    java-attribute="shippingAddress"
                    type="forum18068176.Address"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Demo

Below is some standalone demo code you can run to see how everything works. You will need to specify MOXy as your JAXB provider (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).

package forum18068176;

import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(1);
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "forum18068176/oxm.xml");
        properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
        properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Customer.class}, properties);

        // Unmarshal JSON
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        StreamSource json = new StreamSource("src/forum18068176/input.json");
        Customer customer = unmarshaller.unmarshal(json, Customer.class).getValue();

        // Access Extension Properties
        String lastName = customer.<String>get("lastName");
        Address shippingAddress = customer.<Address>get("shippingAddress");

        // Marshal Objects
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }

}

input.json/Output

{
   "firstName" : "Jane",
   "lastName" : "Doe",
   "billingAddress" : {
      "street" : "1 A Street"
   },
   "shippingAddress" : {
      "street" : "2 B Road"
   }
}

MOXy's @XmlVariableNode extension with an XmlAdapter

If all of the unknown items are of the same type then you can use a combination of MOXy's @XmlVariableNode (see: http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-json-schema.html) and XmlAdapter to get the desired result:

Upvotes: 5

Michal Gajdos
Michal Gajdos

Reputation: 10379

You can use jersey-media-json-jackson module instead of MOXy to take advantage of the annotation (I am not sure whether MOXy has support for a similar feature). Just few notes:

  • remove the com.sun.jersey.api.json.POJOMappingFeature init parameter from your web.xml - Jersey 2.x does not recognize this property (it's Jersey 1.x specific)
  • remove org.glassfish.jersey.media:jersey-media-moxy:2.0 dependency
  • add org.glassfish.jersey.media:jersey-media-json-jackson:2.0 dependency and register JacksonFeature in your application (see below)

After these steps Jackson should be handling Object<->JSON (un)marshalling and it should recognize @JsonAnySetter.

To register JacksonFeature in your application, see dedicated section in users guide (8.1.4 Jackson):

// Create JAX-RS application.
final Application application = new ResourceConfig()
        .packages("org.glassfish.jersey.examples.jackson")
        .register(MyObjectMapperProvider.class)  // No need to register this provider if no special configuration is required.
        .register(JacksonFeature.class);

Upvotes: 8

StaxMan
StaxMan

Reputation: 116572

Jersey 2.0 should work fine with unmodified Jackson 2.x JAX-RS providers from https://github.com/FasterXML/jackson-jaxrs-providers. There is no need to configure anything via web.xml, as provider uses Service Provider Interface. The only potential problem would come from having multiple providers for same media type.

Just make sure to use 2.x (like 2.2.2) versions of Jackson dependencies. While Jackson 1.x and 2.x versions can co-exist, dependencies between modules must match (that is, 2.2 JAX-RS provider depends on 2.2 jackson-core and jackson-databind jars).

Upvotes: 2

Related Questions