Jackson is equivalent to @XmlSeeAlso

I am writing a RESTful web service using Java and Jersey where the service will accept either XML or JSON inputs. Jackson is used as a JSON deserializer and is integrated into the Jersey configuration.

One endpoint is a POST request for a URL where the content can be one of several different Java classes and there is a common base class. These classes - with XML annotations - are:

@XmlRootElement(name = "action") @XmlAccessorType(XmlAccessType.NONE) @XmlSeeAlso({ FirstAction.class, SecondAction.class, ThirdAction.class }) public abstract class BaseAction { } @XmlRootElement(name = "first-action") @XmlAccessorType(XmlAccessType.NONE) public class FirstAction extends BaseAction implements Serializable { } // Likewise for SecondAction, ThirdAction 

In my resource, I can declare a method like:

 @POST @Path("/{id}/action") public Response invokeAction(@PathParam("id") String id, BaseAction action) {...} 

Then I can POST an XML fragment that looks like <firstAction/> , and my method will be called by an instance of FirstAction . So far so good.

Where I struggle to make JSON deserialization work as easily as XML deserialization. If the @XmlSeeAlso annotation was critical for XML deserialization to work correctly, it seemed like the equivalent for JSON was @JsonSubTypes . Therefore, I annotated the classes as follows:

 // XML annotations removed for brevity, but they are present as in the previous code snippet @JsonSubTypes({ @JsonSubTypes.Type(name = "first-action", value = FirstAction.class), @JsonSubTypes.Type(name = "second-action", value = SecondAction.class), @JsonSubTypes.Type(name = "third-action", value = ThirdAction.class) }) public abstract class BaseAction { } @JsonRootName("first-action") public class FirstAction extends BaseAction implements Serializable { } // Likewise for SecondAction, ThirdAction 

Then I pass it my test input: { "first-action": null } , but all I can get is:

"org.codehaus.jackson.map.JsonMappingException: The root name" first-action "does not match the expected (" action ") for type [simple type, class com.alu.openstack.domain.compute.server.actions.BaseAction]"

Unfortunately, since I'm trying to be compatible with someone else's API, I cannot change my input example - { "first-action": null } should work and deliver an object of class FirstAction to my method. (The action has no fields, so null should not be a problem - this is the type of an important class).

What is the proper way to JSON deserialize to work just like XML deserialization?

+1
source share
3 answers

I researched the use of @JsonTypeInfo , but ran into problems because I could not change the input format. The parser absolutely should have been able to handle input { "first-action":null } . This excluded the possibility of adding the @type or @class . Using the wrapper object might have worked, but it clogged up on the null payload.

The important point was that I used the UNWRAP_ROOT_PROPERTY configuration parameter. Jackson absolutely insisted on looking for the action property, and I could not get him to consider anything else. Thus, I had to selectively disable UNWRAP_ROOT_PROPERTY for certain domain objects so that Jackson was open to analyze alternatives. I changed the implementation of the ContextResolver.getContext (...) project to check the @JsonRootName annotation - since this only makes sense if the wrapper is on, I used the presence of this annotation to determine whether to return the object mapper configured with root property wrapping on or off.

At this point, I could use @JsonTypeInfo(include=JsonTypeInfo.As.WRAPPER_OBJECT, ...) , except for the null payload problem mentioned above (this is used to indicate that the child does not have properties - if spec, which I worked from gave an empty object {}, and then there would be no problem). Therefore, to continue, I needed a custom resolver type.

I created a new class that extended org.codehaus.jackson.map.TypeDeserializer so that whenever Jackson is called to deserialize the BaseAction instance, it will call this custom deserializer. An array of subtypes will be presented in the deserializer, which for BaseAction displays the first-action , second-action , etc. In FirstAction.class, etc. Deserializer reads the input stream for the field name, and then maps the name to the class. If the next token is an object, it finds and delegates the appropriate deserializer for this class, or if it is zero, it finds the no-args constructor and calls it to get the object.

It requires a class that implements org.codehaus.jackson.map.jsontype.TypeResolverBuilder, which can instantiate this previous class, and then TypeResolverBuilder is indicated as the @JsonTypeResolver annotation in the BaseAction class.

0
source

If you use Jackson, you are looking for @JsonTypeInfo and @Type . See here for more details.

+2
source

JSON does not work the way XML does, so the solution is not identical.

What you need to use (for example, the answer is different) is @JsonTypeInfo . This only causes the inclusion and use of the type identifier. If so, then "@JsonSubTypes" will be used in deserialization.

The reason this indicator needs to be used is simple: if you have several alternative types for deserialization, you need to distinguish something.

Note that this should NOT be a property - while most users choose to include "As.PROPERTY", this is not the best way (IMO). "WRAPPER_OBJECT" might be what you are looking for, as it adds an extra intermediate JSON property that is somewhat similar to what XML does.

+1
source

Source: https://habr.com/ru/post/1246010/


All Articles