By no means am I a Jackon/JSON wizard, which is probably evident from the following issue I'm running into:
I have 2 possible data structures I'm receiving.
The first one is called amountTransaction:
{
"amountTransaction": {
"clientCorrelator":"54321",
"endUserId":"tel:+16309700001"
}
}
Which is represented by the following Java object:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonTypeName(value = "amountTransaction")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AmountTransaction {
private String clientCorrelator;
private String endUserId;
...
}
However the amountTransaction object also appears as child element of the paymentTransactionNotification object:
{
"paymentTransactionNotification": {
"amountTransaction": {
"clientCorrelator": "54321",
"endUserId": "tel:+16309700001"
}
}
}
..which I thought would be represented by:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonTypeName(value = "paymentTransactionNotification")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentTransactionNotification {
private AmountTransaction amountTransaction;
...
}
Parsing the JSON with the amountTransaction object alone works fine. It's a pretty straightforward example of a WRAPPER_OBJECT.
However when trying to parse the JSON for the paymentTransactionNotification, I'm getting an exception indicating that it can't properly deal with the amountTransaction as element of the paymentTransactionNotification:
com.fasterxml.jackson.databind.JsonMappingException: Could not resolve type id 'clientCorrelator' into a subtype of [simple type, class com.sf.oneapi.pojos.AmountTransaction]
Any thoughts on how I can properly annotate this so my code can properly deal with both stand alone, as well as encapsulated amountTransaction objects?
By default wrapping root node in Jackson is disabled. You can wrap inner objects but if you want to wrap root node you need to enable jackson feature for it (https://jira.codehaus.org/browse/JACKSON-747):
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationConfig.Feature.WRAP_ROOT_VALUE);
objectMapper.enable(DeserializationConfig.Feature.UNWRAP_ROOT_VALUE);
When you enabled these features you already said Jackson to wrap the root element and you don't need #JsonTypeInfo and #JsonTypeName anymore. You can simple delete them. But now you need to customize the root node name and you can use #JsonRootName for it. Your classes should look like this:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName("amountTransaction")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class AmountTransaction {
private String clientCorrelator;
private String endUserId;
...............
}
And
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName("paymentTransactionNotification")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class PaymentTransactionNotification {
private AmountTransaction amountTransaction;
.............
}
I've tried and Jackson converted both JSON requests as expected.
Related
I'm trying to deserialize a response of either an Object or a List of Objects from our southbound API. Now, I tried just parsing an object or a List of Objects with this code just removing either of the two and it works. So I'm sure either of the parsing I have below works, but when I am trying to combine both parsers into one, I get an error of
Cannot deserialize instance of object out of START_ARRAY token
This is even though the payload with object is correctly formatted and this code below could convert a single object to a List of Object and vice versa.
Below is the code that I have.
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
// #JsonInclude(JsonInclude.Include.NON_NULL)
public final class GetCreditCardInfo {
// Deserializer for List of Objects
#JsonAlias("ns:return")
private List<CreditCardDetails> details;
// Deserializer for an Object
#JsonAlias("ns:return")
private CreditCardDetails detailsObject;
public List<CreditCardDetails> getDetails() {
// List< CreditCardDetails > list = List.of( detailsObject ) ;
if(details == null) {
details = List.of(detailsObject);
} else {
}
return details;
}
}
What the code does above is that i tries to parse either an object or a list of object. If it cant get a list of object, it would then try to parse the object into a list of object, otherwise, it would retain the list of object it got from the southbound API.
What do I do to make this work as I intended?. Thanks
Sample JSON List of Objects
{
"soapenv:Envelope":{
"soapenv:Body":{
"ns:getCreditCardResponse":{
"xmlns:ns":"",
"ns:return":[
{
"ax21:cif":"1"
},
{
"ax21:cif":"2"
}
],
"xmlns:ax21":"",
"xmlns:ax23":""
}
},
"xmlns:soapenv":"http://schemas.xmlsoap.org/soap/envelope/"
}
}
Sample JSON Object:
{
"soapenv:Envelope":{
"soapenv:Body":{
"ns:getCreditCardResponse":{
"xmlns:ns":"",
"ns:return":{
"ax21:cif":"1"
},
"xmlns:cif":""
}
},
"xmlns:soapenv":"http://schemas.xmlsoap.org/soap/envelope/"
}
}
PS:
The Sample JSON Objects could already be deserialized by the two deserializers that I have in the code
I also tried annotating my class with
#JsonInclude(JsonInclude.Include.NON_NULL)
to ignore deserialization errors with non existent json field, but still the issue persist.
Sample: Object Deserializer
#JsonIgnoreProperties(ignoreUnknown = true)
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public final class CreditCardDetails {
#JsonAlias("ax21:cif")
private String cif;
}
You can just add the #JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) over your private List<CreditCardDetails> details; in GetCreditCardInfo class.
This way if there is only one value (ns:return is an object) in the json, it will be deserialized as one-element List in java.
So try changing GetCreditCardInfo to the following:
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
// #JsonInclude(JsonInclude.Include.NON_NULL)
public final class GetCreditCardInfo {
#JsonAlias("ns:return")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<CreditCardDetails> details;
}
I'm extending code from an existing Java class that serializes to and from XML. The existing class is somewhat like this:
#Getter
#JacksonXmlRootElement("element")
public class Element {
#JacksonXmlProperty(localName = "type", isAttribute = true)
private String type;
}
The type field has a finite set of possible values so I created an enum Type with all possible values and (to avoid breaking existing functionality) added a new field to the class, like so:
#Getter
#JacksonXmlRootElement("element")
public class Element {
#JacksonXmlProperty(localName = "type", isAttribute = true)
private String type;
#JacksonXmlProperty(localName = "type", isAttribute = true)
#JsonDeserialize(using = TypeDeserializer.class)
private Type typeEnum;
}
This gives me the following error:
Multiple fields representing property "type": Element#type vs Element#typeEnum
I understand why this is a problem cuz when Jackson would try to serialize the class, two fields in my class map onto the same field in the output XML.
I tried adding a #JsonIgnore on one of the fields and it gets rid of the error but has the side effect of not populating the ignored field either. Is there a way to annotate that a field should be deserialized (while reading XML) but not serialized (while writing XML)?
I really need to keep both fields in the class to not disturb any legacy code that might be using the first field, but at the same time allow newer code to leverage the second field.
Thank you!
I have a class:
#EqualsAndHashCode(callSuper = true)
#Data
public class AppealTemplateDto extends AbstractDto {
private List<AbstractFieldDto> fields;
}
This class contains list of AbstractFieldDto inheritors, e.g.:
#EqualsAndHashCode(callSuper = true)
#Data
#NoArgsConstructor
public class InputFieldDto extends AbstractFieldDto {
private String fieldType = FieldType.INPUT.name();
private String text;
}
Totally, there are near 6-7 inheritors, & AbstractTemplateDto may contain any set of them.
Controller:
#PostMapping
public ResponseEntity<AppealTemplateDto> create(#RequestBody AppealTemplateDto dto) {
return ResponseEntity.ok(service.save(dto));
}
When Jackson trying to parse AppealTemplateDto, it crashes with exception:
Caused by:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of
ru.appeal.template.dto.field.AbstractFieldDto
(no Creators, like default construct, exist): abstract types either
need to be mapped to concrete types, have custom deserializer, or
contain additional type information
As I understand, Jackson can't define, how to cast incoming AbstractFieldDto. Please, advice me, what to do?
The Annotation your are needing are:
#JsonTypeInfo
#JsonSubType
#JsonTypeName
Some explanation: if you have many implementation of your abstract type, Jackson can't guess which type is your json, you need to add a type name in json, for example as a new property (this is one of the strategies):
//tell to jackson where to find the type name
#JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type")
// tell to jackson the implementations to scan
#JsonSubTypes({
#JsonSubTypes.Type(value = InputFieldDto.class, name = "input")
//, ...
})
public class AbstractFieldDto {
}
//tell to jackson what is the type name in json
#JsonTypeName("input")
public class InputFieldDto extends AbstractFieldDto {
private String fieldType = FieldType.INPUT.name();
private String text;
}
What I Want to Do
I want to use Jackson to deserialize a polymorphic type, using the standard #JsonTypeInfo annotation as follows:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = As.EXISTING_PROPERTY,
property = "identifier")
#JsonSubTypes({#Type(value = A.class, name = "A"),
#Type(value = B.class, name = "B")})
abstract Class Base {}
Class A implements Base {
public String identifier = "A";
}
Class B implements Base {
public String identifier = "B";
}
Class Decorated {
public String decoration = "DECORATION";
#JsonUnwrapped
public Base base;
}
/*
Serialized instance of Decorated WITHOUT #JsonUnwrapped:
{
"decoration" : "DECORATION",
"base" : {
"identifier" : "A"
}
}
Serialized instance of Decorated WITH #JsonUnwrapped:
{
"decoration" : "DECORATION",
"identifier" : "A"
}
*/
Related post: Deserialize JSON with Jackson into Polymorphic Types - A Complete Example is giving me a compile error
This can normally be deserialized by Jackson as follows:
public Object deserialize(String body, Class clazz) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(body, clazz);
}
(And this would work if the #JsonUnwrapped annotation were removed)
The Problem
Polymorphic types do not play well with Jackson's #JsonUnwrapped annotation, as discussed in this Jira ticket from 2012:
http://markmail.org/message/pogcetxja6goycws#query:+page:1+mid:pogcetxja6goycws+state:results
Handle polymorphic types with #JsonUnwrapped
Agreed - while fixing things is obviously preferable, improving error messages would be useful if that can't be done.
Unwrapping is one of features where implementations gets complicated enough that any bugs cropping up (on deserialization esp) tend to be antibiotic-resistant...
Hardly encouraging.
Three years later:
http://markmail.org/message/cyeyc2ousjp72lh3
Handle polymorphic types with #JsonUnwrapped
Resolution: Won't Fix
Damn.
So, is there any way to coax Jackson into giving me this behaviour without modifying deserialize() or removing the #JsonUnwrapped annotation?
My SinglePolyUnwrappedDeserializer from this Gist can handle a single polymorphic #JsonUnwrapped property. It's in Kotlin, but can easily be ported to Java if needed. Example:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
#JsonSubTypes(
JsonSubTypes.Type(value = A::class, name = "a"),
JsonSubTypes.Type(value = B::class, name = "b")
)
abstract class Base
data class A(val x: Int) : Base()
data class B(val y: Boolean) : Base()
#JsonDeserialize(using = SinglePolyUnwrappedDeserializer::class)
data class C(val a: String, #JsonUnwrapped val b: Base)
AFAIK, all combinations of other annotations are supported. The only limitation is that there is exactly one #JsonUnwrapped property.
If you also need a generic serializer for polymorphic #JsonUnwrapped, you can write it yourself very easily without any reflection or introspection: just merge the ObjectNode of the inner object onto the ObjectNode of the containing object.
I am trying to serialize/deserialize the following
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = IdBundleCombine.class),
#JsonSubTypes.Type(value = IdBundleDistinct.class) })
public abstract class IdBundle
{
String sharedId;
Long internalId;
//getters
}
public class IdBundleCombine extends IdBundle
{
//setters
}
public class IdBundleDistinct extends IdBundle
{
//setters
}
with the following code
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(new File("foo.json"), someInstanceOfIdBundle);
Which produce the following (without type information as you can see):
{"sharedId":"foobar","internalId":1234}
So i get an error missing property '#type' that is to contain type id when I try to deserialize it.
I tried every combination of parameters for #JsonTypeInfo and #JsonSubTypes I could find without ever succeeding in getting the type information to show in my file. I also tried to play with the #JsonTypeName on the subType without results.
My only guess is that I am doing something wrong with the mapper, but I can't find anything on the subject since most of the people seem to either don't want the type information to show up in the json string, or to have problems with the deserialization process.
I did try using the following annotation and it worked, even with the property tag.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "#type")
Add 'name' attribute to #Type for sub types and give 'property' attribute of #JsonTypeInfo any value of your choice. Class below
<!-- language: java -->
#JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "meta-type")
#JsonSubTypes({#Type(value = IdBundleCombine.class, name = "bundle-combine"),
#Type(value = IdBundleDistinct.class, name = "bundle-distinct")})
public abstract class IdBundle{
}
will produce following json in case it's IdBundleCombine
{"meta-type": "bundle-combine", "sharedId":"foobar","internalId":1234}