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 the JSON deserializer, and integrated into the Jersey config.
One of the endpoints is a POST request to 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 invoked with a FirstAction instance. So far so good.
Where I'm struggling is getting the JSON deserialization to work as seamlessly as the XML deserialization. Where the #XmlSeeAlso annotation was critical to get the XML deserialization working properly, it seemed that the equivalent for JSON was #JsonSubTypes. So I annotated the classes like this:
// 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
I then feed it my test input: { "first-action": null } but all I can get is:
"org.codehaus.jackson.map.JsonMappingException: Root name 'first-action' does not match 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 can't change my sample input - { "first-action": null } has to work, and deliver to my method an object of class FirstAction. (The action doesn't have any fields, which is why null shouldn't be a problem - it's the type of the class that's important).
What's the correct way to have the JSON deserialization work in the same way as the XML deserialization already is?
If you are using Jackson, you are looking for #JsonTypeInfo and #Type . Please see here for more information
JSON does not work the way XML does, so the solution is not identical.
What you need to use is (like the other answer said), #JsonTypeInfo. That only triggers inclusion and use of the type identifier. If so, then '#JsonSubTypes` will be of use in deserialization.
The reason this indicator must be used is simple: if you have more than one alternative type to deserialize to, there must be something to differentiate.
Note, too, that this does NOT have to be a property -- while most users choose "As.PROPERTY" inclusion, it is not (IMO) the best way. "WRAPPER_OBJECT" may be what you are looking for, as it adds an extra intermediate JSON property, which is somewhat similar to what XML does.
I investigated the use of #JsonTypeInfo but ran into problems because I could not alter the input format. The parser absolutely had to be able to handle input { "first-action":null }. This ruled out the possibility of adding an #type or #class property. Using a wrapper object may have worked, but it choked on the null payload.
A crucial point was that I was using the UNWRAP_ROOT_PROPERTY configuration option. Jackson was absolutely insisting on finding an action property and I could not get it to consider anything else. So, I had to selectively disable UNWRAP_ROOT_PROPERTY for certain domain objects, so that Jackson would be open to parsing alternatives. I modified the project's ContextResolver.getContext(...) implementation to check for a #JsonRootName annotation - since this only has meaning if wrapping is enabled, I used the presence of this annotation to determine whether to return an object mapper configured with root property wrapping on, or off.
At this stage, I might have been able to use #JsonTypeInfo(include=JsonTypeInfo.As.WRAPPER_OBJECT, ...), except for the issue with the null payload mentioned above (this is used to indicate that the child object has no properties - if the spec I was working from had given an empty object {} instead then there would not be a problem). So to proceed I needed a custom type resolver.
I created a new class that extended org.codehaus.jackson.map.TypeDeserializer, with the purpose that whenever Jackson is called to deserialize a BaseAction instance, it will call this custom deserializer. The deserializer will be given a subtypes array, which for BaseAction maps first-action, second-action, etc. to FirstAction.class, etc. The deserializer reads the input stream for the field name, then matches the name to a class. If the next token is an object, then it finds and delegates to the appropriate deserializer for that class, or if it is null it finds the no-args constructor and invokes it to get an object.
A class that implements org.codehaus.jackson.map.jsontype.TypeResolverBuilder is needed that can build an instance of this previous class, and then the TypeResolverBuilder is given as a #JsonTypeResolver annotation on the BaseAction class.
Related
I'm using Jackson's readValue() method on an object mapper to read from a JSON file and convert it into my java object.
eg.
mapperObject.readValue( node, MyTargetClass.class )
Are there any annotations that I can set on MyTargetClass to enforce required attributes? For example, if I have a JSON object with properties ABC,DEF and GHI, and my Json is the following
{
"ABC" : "somevalue"
"DEF" : "someothervalue"
}
I want it to fail somehow, and only succeed on the readValue if it contained ABC, DEF and GHI.
You can mark a property as required with the #JsonProperty(required = true) annotation, and it will throw a JsonMappingException during deserialization if the property is missing or null.
Edit: I received a downvote for this without comment. I'd love to know why, since it does exactly the right thing.
Jackson does not include validation functionality, and this is by design (i.e. that is considered out-of-scope). But what is usually used is Bean Validation API implementation.
The nice thing about this is decoupling between data format handling, and validation logic.
This is what frameworks like DropWizard use; and it's the direction JAX-RS (like Jersey) are taking things for JAX-RS 2.0.
If you want to make sure a json field is provided, you have to use the #JsonProperty(value = "fieldName", required = true) annotation as a parameter to the constructor. But this is not enough, also the Constructor should have #JsonCreator annotation.
For example, if you have a field named 'endPoint' and you want o make sure it is provided in the JSON file, then the following code will throw an exception if it is not provided.
#JsonCreator
public QuerySettings(#JsonProperty(value = "endPoint", required = true) String endPoint) {
this.endPoint = endPoint;
}
I found this link helpful to understand the Jackson annotations. It also well explains why required=true is not enough and counter-intuitive to its name.
If you are neither satisfied with using #JsonProperty(required = true) as it works only with #JsonCreator nor with the use of bean validation then one more way of tackling it would be to catch this in your setter methods for the relevant variables.
You can simply check if the variable is null before setting it and throw an IllegalArgumentException or NullPointerException (as preferred by few people)
Note: It depends on how your POJO is defined too, so please make sure that it is going the setter method route for this solution to work.
We have a pojo generated from yml which has #JsonProperty defined on the getter. This is based on maven plugin for openapi. For one such yml, we have a field settlementType. We can't change the name as this is the field to be sent to the vendor in the rest api call. When using jackson's ObjectMapper to deserializa the object, we get the following error -
Conflicting setter definitions for property "settlementType":
settlementType(1 param) vs setSettlementType(1 param).
I tried for a mixin class.
public class SettlementTypeMixin {
#JsonProperty("settlementType"
String settlementType;
#JsonIgnore
public void setSettlementType(String settlementType) {
this.settlementType = settelementType;
}
}
I added the mixin to the mapper using addMixinAnnotations method. But I don't see any change in the behavior.
I also tried setting the fieldVisibility, getterVisibility, setterVisibility and CreatorVisibilities as well. There are other types as well which the yml needs and at the end I have to enable the visibility for all the types mentioned below.
I am not able to get the object mapped using jackson. Please let me know if there is a way out for this.
I am using jackson as part of serializing and deserializing in my project (Spring Java).
In normal scenarios where I have interface(contract) acting as field in POJO,
then I use #JsonTypeInfo and #JsonSubTypes to achieve deserialization in polymorphic cases.
But, right now, I have scenariio something like this:
public class classA {
private contractA fieldA;
//constructor and getter-setters.
}
then,
public interface contractA {
}
and finally,
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(/* concrete-class1 as name-value */),
#JsonSubTypes.Type(/* concrete-class2 as name-value */),
})
public interface contractB extends contractA {
//contract methods.
}
Now, when classA is passed as controller request body and I pass fieldA as concrete-class1 or concrete-class2,
JsonSubTypes are not being used by jackson to deserialize into one of them.
The reason why I did this and had two contracts is due to package dependencies. contractB
is in different package as of contractA's.
How can I configure on contractA using jackson that this class has its JsonSubTypeInfo specified in its subclasses.
Or, any other libraries or approaches are also welcomed.
Thank you !
This problem is later on solved by introducing our own custon JsonTypeInfo.
When the application is under deployment, we fetch all subclasses which is present in the JsonTypeInfo annotation (jackson like custom annotaion) and maintain a data-structure, that will be used while serializing and deserializing. This process is somewhat similar to the Jackson one (in addition to lookup for nested hierarches as well).
I have a group which contains a list of persons:
class Person {
...
}
class Group {
public Person findPerson(String name) {
...
}
}
Say I have an input JSON (representation of SomeDataClass - see below) which refer to a person by its name:
{
...
"person" : "Bill"
}
I am using Jackson to parse this input JSON. By default, Jackson parses this the person field to a String. Is it possible to change this, such that the person is resolved/looked up during parsing?
class SomeDataClass {
...
#JsonProperty("person")
protected Person person;
}
Note that I do not want to create a new person. I want to look it up, by calling the function getPerson on an instance of Group. This means that I must have access to the group during the parsing. There are several groups at runtime, so it is not singleton.
update
I am aware of the #JsonDeserialize(using = XYZ.cass) possibility, but this does not allow me to pass the group to the custom deserializer. As said, there are multiple groups, so it is not singleton.
I do not think this is possible with Jackson. You could try to store your reference to the group in a ThreadLocal, so your deserializer is using the correct group.
Jackson does have support for Object Ids, via #JsonIdentityInfo annotation. But it is assumed that references using ids ("Bill" in this case) may be resolved by matching definitions within JSON content, so this may not work for your case.
You may need to handle resolution yourself; if you define setPerson(String), method itself could try locating actual instance to use. But that does require use of ThreadLocal, as mentioned.
Another alternative could be custom deserializer, which would use "attribute"s via DeserializationContext; but you still need to provide such mappings so it does not help a lot.
I'm using Jackson's readValue() method on an object mapper to read from a JSON file and convert it into my java object.
eg.
mapperObject.readValue( node, MyTargetClass.class )
Are there any annotations that I can set on MyTargetClass to enforce required attributes? For example, if I have a JSON object with properties ABC,DEF and GHI, and my Json is the following
{
"ABC" : "somevalue"
"DEF" : "someothervalue"
}
I want it to fail somehow, and only succeed on the readValue if it contained ABC, DEF and GHI.
You can mark a property as required with the #JsonProperty(required = true) annotation, and it will throw a JsonMappingException during deserialization if the property is missing or null.
Edit: I received a downvote for this without comment. I'd love to know why, since it does exactly the right thing.
Jackson does not include validation functionality, and this is by design (i.e. that is considered out-of-scope). But what is usually used is Bean Validation API implementation.
The nice thing about this is decoupling between data format handling, and validation logic.
This is what frameworks like DropWizard use; and it's the direction JAX-RS (like Jersey) are taking things for JAX-RS 2.0.
If you want to make sure a json field is provided, you have to use the #JsonProperty(value = "fieldName", required = true) annotation as a parameter to the constructor. But this is not enough, also the Constructor should have #JsonCreator annotation.
For example, if you have a field named 'endPoint' and you want o make sure it is provided in the JSON file, then the following code will throw an exception if it is not provided.
#JsonCreator
public QuerySettings(#JsonProperty(value = "endPoint", required = true) String endPoint) {
this.endPoint = endPoint;
}
I found this link helpful to understand the Jackson annotations. It also well explains why required=true is not enough and counter-intuitive to its name.
If you are neither satisfied with using #JsonProperty(required = true) as it works only with #JsonCreator nor with the use of bean validation then one more way of tackling it would be to catch this in your setter methods for the relevant variables.
You can simply check if the variable is null before setting it and throw an IllegalArgumentException or NullPointerException (as preferred by few people)
Note: It depends on how your POJO is defined too, so please make sure that it is going the setter method route for this solution to work.