Cannot deserialize generic class hierarchy using Jackson - java

I'm receiving, for instance, this JSON from an external vendor (where payload can be variable):
{
"payload": {
"enrolledAt": "2018-11-05T00:00:00-05:00",
"userId": "99c7ff5c-2c4e-423f-abeb-2e5f3709a42a"
},
"requestId": "80517bb8-2a95-4f15-9a73-fcf3752a1147",
"eventType": "event.success",
"createdAt": "2018-11-05T16:55:13.762-05:00"
}
I'm trying to model these using this class:
public final class Notification<T extends AbstractModel> {
#JsonProperty("requestId")
private String requestId;
#JsonProperty("eventType")
private String eventType;
#JsonProperty("createdAt")
private ZonedDateTime createdAt;
private T payload;
#JsonCreator
public Notification(#JsonProperty("payload") T payload) {
requestId = UUID.randomUUID().toString();
eventType = payload.getType();
createdAt = ZonedDateTime.now();
this.payload = payload;
}
// getters
}
...and then having these possible (generic) types:
public abstract class AbstractModel {
private String userId;
private Type type;
#JsonCreator
AbstractModel(#JsonProperty("companyUserId") String userId, #JsonProperty("type") Type type) {
this.userId = userId;
this.type = type;
}
// getters
public enum Type {
CANCEL("event.cancel"),
SUCCESS("event.success");
private final String value;
Type(String value) {
this.value = value;
}
public String getValue() { return value; }
}
}
public final class Success extends AbstractModel {
private ZonedDateTime enrolledAt;
#JsonCreator
public Success(String userId, #JsonProperty("enrolledAt") ZonedDateTime enrolledAt) {
super(userId, Type.SUCCESS);
this.enrolledAt = enrolledAt;
}
// getters
}
public final class Cancel extends AbstractModel {
private ZonedDateTime cancelledAt;
private String reason;
#JsonCreator
public Cancel(String userId, #JsonProperty("cancelledAt") ZonedDateTime cancelledAt,
#JsonProperty("reason") String reason) {
super(userId, Type.CANCEL);
this.cancelledAt = cancelledAt;
this.reason = reason;
}
// getters
}
The application is based on Spring Boot, so I'm deserializing the JSON like:
#Component
public final class NotificationMapper {
private ObjectMapper mapper;
public NotificationMapper(final ObjectMapper mapper) {
this.mapper = mapper;
}
public Optional<Notification<? extends AbstractModel>> deserializeFrom(final String thiz) {
try {
return Optional.of(mapper.readValue(thiz, new NotificationTypeReference()));
} catch (final Exception e) { /* log errors here */ }
return Optional.empty();
}
private static final class NotificationTypeReference extends TypeReference<Notification<? extends AbstractModel>> { }
}
...but eventually since I'm posting this right here, Jackson doesn't like any of that so far. I've tried several things like: JsonTypeInfo and JsonSubTypes, but I can't change the JSON input.
Anyone? Any clue(s)?

We ended up adding another key/value pair in the JSON – the contract was indeed modified a little bit to accommodate that "private" key/value pair.
In any case, if somebody runs into the same issue, this is the solution for the approach:
...
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
#JsonIgnoreProperties("_type")
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "_type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Cancel.class, name = "_type"),
#JsonSubTypes.Type(value = Fail.class, name = "_type"),
#JsonSubTypes.Type(value = Success.class, name = "_type"),
})
public abstract class AbstractModel {
private String userId;
private Type type;
AbstractModel() { }
AbstractModel(final String userId, final Type type) {
this.userId = userId;
this.type = type;
}
// getters, toString, etc.
public enum Type {
CANCEL("event.cancelled"),
FAIL("event.failed"),
SUCCESS("event.success");
private final String value;
Type(String value) {
this.value = value;
}
public String getValue() { return value; }
}
}

Related

Error serializing and deserializing json using polymorphism in java

I'm trying to manage different value structure using polimorphism. The next example contains 2 jsons that should be deserialized into the same parent object
{
"maximum": {
"type": "type1",
"value": {
"USD": 1000000,
"GBP": 500000
}
}
}
{
"maximum": {
"type": "type2",
"linkedTo": "linked to item",
"value": 2
}
}
I have defined the next classes to manage that:
#JsonInclude (Include.NON_EMPTY)
#JsonDeserialize (builder = Parent.Builder.class)
public class Parent {
private ParentType maximum;
private Parent(Builder builder) {
this.maximum = builder.maximum;
}
public static Builder builder() { return new Builder(); }
public ParentType getMaximum() {
return maximum;
}
public static class Builder {
private ParentType maximum;
public Builder withMaximum(final ParentType maximum) {
this.maximum = maximum;
return this;
}
public Parent build() {
return new Parent(this);
}
}
}
#JsonInclude (Include.NON_EMPTY)
#JsonDeserialize (builder = ParentType.Builder.class)
public class ParentType {
private String type;
private String linkedTo;
private ParentTypeValue value;
private ParentType(Builder builder) {
this.type = builder.type;
this.linkedTo = builder.linkedTo;
this.value = builder.value;
}
public static Builder builder() { return new Builder(); }
public String getType() {
return type;
}
public String getLinkedTo() {
return linkedTo;
}
public ParentTypeValue getValue() {
return value;
}
public static class Builder {
private String type;
private String linkedTo;
private ParentTypeValue value;
public Builder withType(final String type) {
this.type = type;
return this;
}
public Builder withLinkedTo(final String linkedTo) {
this.linkedTo = linkedTo;
return this;
}
public Builder withValue(final ParentTypeValue value) {
this.value = value;
return this;
}
public ParentType build() {
return new ParentType(this);
}
}
}
And finally I tried the polymorphism of Value with the next structure:
#JsonSubTypes ({
#JsonSubTypes.Type(value = CurrencyMapValue.class, name = "currencymapType"),
#JsonSubTypes.Type(value = StringValue.class, name = "integerType"),
})
public abstract class ParentTypeValue {
}
#JsonTypeName ("integerType")
public class IntegerValue extends ParentTypeValue {
private int value;
private IntegerValue(Builder builder) {
this.value = builder.value;
}
public static Builder builder() { return new Builder(); }
public int getValue() {
return value;
}
public static class Builder {
private int value;
public Builder withValue(final int value) {
this.value = value;
return this;
}
public IntegerValue build() {
return new IntegerValue(this);
}
}
}
#JsonTypeName ("currencymapType")
public class CurrencyMapValue extends LimitTypeValue {
private Map<Currency, Double> value;
private CurrencyMapValue(Builder builder) {
this.value = builder.value;
}
public static Builder builder() { return new Builder(); }
public Map<Currency, Double> getValue() {
return value;
}
public static class Builder {
private Map<Currency, Double> value;
public Builder withValue(final Map<Currency, Double> value) {
this.value = value;
return this;
}
public CurrencyMapValue build() {
return new CurrencyMapValue(this);
}
}
}
I'm guessing the polymorphic structure is wrong as I'm not able to deserialize and serialize is adding an extra value object inside the wanted value. I mean something like that:
"maximum": {
"type": "amount",
"linkedTo": "linkedTo",
"value": {
**"value": {**
"USD": 5000000,
"GBP": 200000
}
}
}
Does anyone have an idea about what is wrong on that?. I appreciate very much your help!
Try this..
Please note that #JsonSubTypes and #JsonTypeInfo annotations are added inside Maximum class.
You dont need #JsonTypeName annotation in your CurrencyMapValue and IntegerValue classes if you are only de-serializing. Yet I have added them.
public abstract class ParentTypeValue {}
#Getter
#Builder
#JsonDeserialize(builder = CurrencyMapValue.CurrencyMapValueBuilder.class)
#JsonTypeName("type1")
public class CurrencyMapValue extends ParentTypeValue {
private Map<String, Double> value;
#JsonPOJOBuilder(withPrefix="")
public static class CurrencyMapValueBuilder {}
}
#Getter
#Builder
#JsonDeserialize(builder = IntegerValue.IntegerValueBuilder.class)
#JsonTypeName("type2")
public class IntegerValue extends ParentTypeValue {
private int value;
private String linkedTo;
#JsonPOJOBuilder(withPrefix="")
public static class IntegerValueBuilder {}
}
#Getter
#Setter
public class Maximum {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = CurrencyMapValue.class, name = "type1"),
#JsonSubTypes.Type(value = IntegerValue.class, name = "type2"),
})
private ParentTypeValue maximum;
}

Retrofit: Parse nested JSON elements to a list

I'm having difficulty trying to parse this JSON response into a list of "properties" elements. My JSON looks like this:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"mag": 6.6,
"place": "192km ESE of Tadine, New Caledonia"
}
},
{
"type": "Feature",
"properties": {
"mag": 7.5,
"place": "168km ESE of Tadine, New Caledonia"
}
},
{
"type": "Feature",
"properties": {
"mag": 6,
"place": "155km ESE of Tadine, New Caledonia"
}
}
]
}
This is response contains Earthquake details so basically each "properties" within "features" is the POJO I want, but all of them just in a List. Here is my Earthquake class:
public class Earthquake {
#SerializedName("mag")
private double magnitude;
#SerializedName("place")
private String location;
public Earthquake(double magnitude, String location) {
this.magnitude = magnitude;
this.location = location;
}
// getters
}
I've tried doing custom deserialization suggested here. It gives me the error
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
suggesting that I'm trying to parse a JsonObject instead of a JsonArray. Here is the deserializer I used.
public class EarthquakeDeserializer implements JsonDeserializer<ArrayList<Earthquake>> {
#Override
public ArrayList<Earthquake> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
// get list of "features"
JsonElement features = json.getAsJsonObject().get("features");
JsonArray earthquakeElements = new JsonArray();
for (JsonElement feature : features.getAsJsonArray()){
JsonElement properties = feature.getAsJsonObject().get("properties");
earthquakeElements.add(properties);
}
Type listType = new TypeToken<ArrayList<Earthquake>>(){}.getType();
return new Gson().fromJson(earthquakeElements, listType);
}
}
Any ideas as to what's going on here?
you can create this kind of a POJO class for your Json, No matter if you want just single part of your response body, you need to create POJO for whole response and from that POJO you need to get appropriate attributes. ->
This is your main json object ->
public class Example {
#SerializedName("type")
#Expose
private String type;
#SerializedName("features")
#Expose
private List<Feature> features = null;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Feature> getFeatures() {
return features;
}
public void setFeatures(List<Feature> features) {
this.features = features;
}
}
this is your feature class ->
package com.example;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Feature {
#SerializedName("type")
#Expose
private String type;
#SerializedName("properties")
#Expose
private Properties properties;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
this is your properties class ->
package com.example;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Properties {
#SerializedName("mag")
#Expose
private Integer mag;
#SerializedName("place")
#Expose
private String place;
public Integer getMag() {
return mag;
}
public void setMag(Integer mag) {
this.mag = mag;
}
public String getPlace() {
return place;
}
public void setPlace(String place) {
this.place = place;
}
}
After creating this classes, you can serialize the JSON to POJO via GSON library, you can refer to HussainAbbas's answer for how to do it.
Now you can get anything via creating object of response class, and via that object you can access any property you want. Thanks.
check this out
package com.example;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Example {
#SerializedName("type")
#Expose
private String type;
#SerializedName("features")
#Expose
private List<Feature> features = null;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Feature> getFeatures() {
return features;
}
public void setFeatures(List<Feature> features) {
this.features = features;
}
}
-----------------------------------com.example.Feature.java-----------------------------------
package com.example;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Feature {
#SerializedName("type")
#Expose
private String type;
#SerializedName("properties")
#Expose
private Properties properties;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
-----------------------------------com.example.Properties.java-----------------------------------
package com.example;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Properties {
#SerializedName("mag")
#Expose
private Integer mag;
#SerializedName("place")
#Expose
private String place;
public Integer getMag() {
return mag;
}
public void setMag(Integer mag) {
this.mag = mag;
}
public String getPlace() {
return place;
}
public void setPlace(String place) {
this.place = place;
}
}
After this add this... in your retrofit response
Gson gson = new Gson()
String data = gson.toJson(response.body());
LoginResponse loginResponse = gson.fromJson(dataString,LoginResponse.class);
Example example = gson.fromJson(dataString,Example.class);
String feature_proprerties_mag = example.getFeatures().getProperties().getMag

Handle different types for some fields while serializing and deserializing with Jackson

I am using Jackson 2.
When reading from remote service I obtain a JSON object like this:
{
"country":"1",
"complex":{
"link":"...",
"value":"..."
},
"test":""
}
so I have created the related POJOs for the purpose:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"link",
"value"
})
public class Complex {
#JsonProperty("link")
private String link;
#JsonProperty("value")
private String value;
#JsonProperty("link")
public String getLink() {
return link;
}
#JsonProperty("link")
public void setLink(String link) {
this.link = link;
}
#JsonProperty("value")
public String getValue() {
return value;
}
#JsonProperty("value")
public void setValue(String value) {
this.value = value;
}
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"country",
"complex",
"test"
})
public class Resource {
#JsonProperty("country")
private String country;
#JsonProperty("complex")
private Complex complex;
#JsonProperty("test")
private String test;
#JsonProperty("country")
public String getCountry() {
return country;
}
#JsonProperty("country")
public void setCountry(String country) {
this.country = country;
}
#JsonProperty("complex")
public Complex getComplex() {
return complex;
}
#JsonProperty("complex")
public void setComplex(Complex complex) {
this.complex = complex;
}
#JsonProperty("test")
public String getTest() {
return test;
}
#JsonProperty("test")
public void setTest(String test) {
this.test = test;
}
}
so that I'm able to do:
Resource res = MAPPER.readValue(response.readEntity(String.class), Resource.class);
My problem is that, when executing POST requests, I need to send a different object, that is:
{
"country":"1",
"complex": "value",
"test":""
}
so where all "complex" objects must be simply strings.
Any idea about how to handle this situation?
I have tried to create a JsonDeserializer class:
public class ComplexValueDeserializer extends JsonDeserializer<Object> {
#Override
public Object deserialize(final JsonParser parser, final DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
final JsonToken jsonToken = parser.getCurrentToken();
if (jsonToken == null
|| (jsonToken == JsonToken.START_OBJECT && parser.getValueAsString().equals("{}"))) {
return "";
}
return null;
}
}
and add it to:
#JsonProperty("complex")
#JsonDeserialize(using = ComplexValueDeserializer.class)
private Complex complex;
but I get java.lang.IllegalArgumentException: argument type mismatch error.
Thanks!
On your Complex Object type you can define a toString() method and then Annotate it with #JsonValue. This will indicate to Jackson that the returned result will be the value serialized for that object. You can them implement whatever logic you need there to represent the Complex type in the way you want. e.g.:
#JsonValue
public String toString() {
return value;
}
Why do you need
"complex":{
"link":"...",
"value":"..."
},
for api documentation?

Polymorphic deserialization of JSON file with Jackson

Depending on the content of a JSON file, I want to deserialize it either to a superclass or subclass.
It should be deserialized to the superclass if it looks like this:
{
"id":"123",
"title":"my title",
"body":"my body"
}
Or to the subclass if it looks like this:
{
"id":"123",
"title":"my title",
"body":"my body",
"tags":["tag1", "tag2"]
}
So the only difference is the tags array, which should be deserialized to a String array.
But if I trigger the deserialization in Jersey (Dropwizard) via POST request, it returns {"code":400,"message":"Unable to process JSON"}.
This is the superclass:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({ #JsonSubTypes.Type(name = "subdocument", value = SubDocument.class) })
public class SuperDocument {
private String id;
private String title;
private String body;
public SuperDocument() {
}
#JsonCreator
public SuperDocument(#JsonProperty("id") String id, #JsonProperty("title") String title, #JsonProperty("body") String body) {
this.id = id;
this.title = title;
this.body = body;
}
#JsonProperty("id")
public String getId() {
return id;
}
#JsonProperty("id")
public void setId(String id) {
this.id = id;
}
... the other getters and setters ...
}
This is the subclass:
#JsonTypeName("subdocument")
public class SubDocument extends SuperDocument {
private String[] tags;
public SubDocument() {
}
#JsonCreator
public SubDocument(#JsonProperty("id") String id, #JsonProperty("title") String title, #JsonProperty("body") String body, #JsonProperty("tags") String[] tags) {
super(id, title, body);
this.tags = tags;
}
#JsonProperty("tags")
public String[] getTags() {
return tags;
}
#JsonProperty("tags")
public void setTags(String[] tags) {
this.tags = tags;
}
}
Do you know what I am doing wrong?
JsonTypeInfo require a property that can identify your sub-class/super class. For eg:
{
"id":"123",
"title":"my title",
"body":"my body",
"type":"superdocument"
}
and
{
"id":"123",
"title":"my title",
"body":"my body",
"tags":["tag1", "tag2"],
"type":"subdocument"
}
Then modify SuperDocument annotations as shown below.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,property="type")
#JsonSubTypes({ #JsonSubTypes.Type(name = "subdocument", value = SubDocument.class),#JsonSubTypes.Type(name = "superdocument", value = SuperDocument.class) })
public class SuperDocument {
}
If you don't want to intrduce an additional property "type", then you may have to write a custom type resolver and type deserializer as shown below.
public class DocumentTypeResolver extends StdTypeResolverBuilder {
#Override
public TypeDeserializer buildTypeDeserializer(
final DeserializationConfig config, final JavaType baseType, final Collection<NamedType> subtypes) {
return new DocumentDeserializer(baseType, null,
_typeProperty, _typeIdVisible, _defaultImpl);
}
}
Custom TypeDeserializer
public static class DocumentDeserializer extends AsPropertyTypeDeserializer {
public DocumentDeserializer(final JavaType bt, final TypeIdResolver idRes, final String typePropertyName, final boolean typeIdVisible, final Class<?> defaultImpl) {
super(bt, idRes, typePropertyName, typeIdVisible, defaultImpl);
}
public DocumentDeserializer(final AsPropertyTypeDeserializer src, final BeanProperty property) {
super(src, property);
}
#Override
public TypeDeserializer forProperty(final BeanProperty prop) {
return (prop == _property) ? this : new DocumentDeserializer(this, prop);
}
#Override
public Object deserializeTypedFromObject(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
JsonNode node = jp.readValueAsTree();
Class<?> subType =null;
JsonNode tags = node.get("tags");
if (tags == null) {
subType=SuperDocument.class;
} else {
subType=SubDocument.class;
}
JavaType type = SimpleType.construct(subType);
JsonParser jsonParser = new TreeTraversingParser(node, jp.getCodec());
if (jsonParser.getCurrentToken() == null) {
jsonParser.nextToken();
}
JsonDeserializer<Object> deser = ctxt.findContextualValueDeserializer(type, _property);
return deser.deserialize(jsonParser, ctxt);
}
}
Now annotate your SuperDocument class as shown below
#JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
#JsonTypeResolver(DocumentTypeResolver.class)
public class SuperDocument {
}

Jaxb dont discovery correct son of my base class

In a personal project i have the following xml structures to make my own java classes:
first:
<response>
<action>getcredits</action>
<data>
<account>
<username>aptalaser</username>
<balance>193</balance>
</account>
</data>
second:
<response>
<action>getMsgInfoByID</action>
<data>
<messageid>c36d7ee5-16f9-4193-9a75-0537e590e9d3</messageid>
<originator>+17036231081 [4]</originator>
<recipient>10958</recipient>
<folder>INBOX</folder>
<senttime>2011.10.17 13:10:26</senttime>
<receivedtime>2011.10.17 13:10:26</receivedtime>
<creationtime>2011.10.17 13:10:26</creationtime>
<callbackid/>
<state>0</state>
<operatornames>
<operatorname>SMPP0</operatorname>
</operatornames>
<routes>
<route>defin_admin</route>
</routes>
<optionalfields>
<optionalfield name="30" value="35333131572D31303133322D303530364E2D333434544600"/>
<optionalfield name="8528" value="017F"/>
</optionalfields>
<messagetype>SMS:TEXT</messagetype>
<messagedata>Test message</messagedata>
</data>
third:
<response>
<action>sendmessage</action>
<data>
<acceptreport>
<statuscode>0</statuscode>
<statusmessage>Message accepted for delivery</statusmessage>
<messageid>0f06bbd9-0894-4fb4-9c4b-68e29363d299</messageid>
<originator>aptalaser</originator>
<recipient>8588430260</recipient>
<messagetype>SMS:TEXT</messagetype>
<messagedata>Bom dia cara.</messagedata>
</acceptreport>
</data>
The structures are divided in two places: a action to indicating the method acessed and the data section: a generic portion with will change the content dinamically in response to access a different method.
Following this idea i decide create a Response class with two field: a String action and a field Data:
The Response class:
/* omite desnecessary imports */
#XmlRootElement(name = "response")
#XmlAccessorType(XmlAccessType.PROPERTY)
public class Response {
private String action;
private Data data;
public String getAction() {
return action;
}
/* omitted set methds */
#XmlElementRef
public Data getData() {
return data;
}
#Override
public String toString() {
String template = "( action: %s, data: %s )";
return String.format(template, this.action, this.data);
}
}
The Data class, the base class for all Data sections
#XmlSeeAlso({ GetInfoMessageData.class, GetAccountData.class, SendMessageData.class })
public class Data {
}
The GetAccountClass to represent account retrieve information
/* omite desnecessary imports */
#XmlRootElement(name = "data")
#XmlAccessorType(XmlAccessType.PROPERTY)
public class GetAccountData extends Data {
private List<Account> account;
public List<Account> getAccount() {
return account;
}
/* omitted set methos */
#Override
public String toString() {
return String.format("Account( %s )", this.account);
}
public static class Account {
private String username;
private Long balance;
public String getUsername() {
return username;
}
public Long getBalance() {
return balance;
}
/* omitted set methods */
#Override
public String toString() {
return String.format("[ usr: %s, credit: %d ]", this.username, this.balance);
}
}
}
The class represents the message data
/* omite desnecessary imports */
#XmlRootElement(name = "data")
#XmlAccessorType(XmlAccessType.PROPERTY)
public class GetInfoMessageData extends Data {
private String messageId;
private String destino;
private String recipiente;
private String folder;
private Date dataCricao;
private Date dataEnvio;
private Date dataRecebimento;
private Integer status;
private String tipoMensagem;
private String mensagem;
private List<Protocolo> protocolos;
private List<Route> rotas;
private List<Field> optionalFields;
private Error error;
#XmlAccessorType(XmlAccessType.PROPERTY)
public static class Protocolo {
private String nomeProtocolo;
#XmlElement(name = "operatorname", defaultValue = "")
public String getNomeProtocolo() {
return nomeProtocolo;
}
/* omitted set methods */
}
#XmlAccessorType(XmlAccessType.PROPERTY)
public static class Error {
private String errorMessage;
#XmlElement(name = "errormessage")
public String getErrorMessage() {
return errorMessage;
}
/* omitted set methods */
}
#XmlAccessorType(XmlAccessType.PROPERTY)
public static class Route {
private String route;
#XmlElement(defaultValue = "")
public String getRoute() {
return route;
}
/* omitted set methods */
}
#XmlAccessorType(XmlAccessType.PROPERTY)
public static class Field {
private String name;
private String value;
#XmlAttribute
public String getName() {
return name;
}
#XmlAttribute
public String getValue() {
return value;
}
/* omitted set methods */
}
#XmlElement(name = "messageid", required = true)
public final String getMessageId() {
return messageId;
}
#XmlElement(name = "originator", required = true)
public final String getDestino() {
return destino;
}
#XmlElement(name = "recipient", defaultValue = "")
public final String getRecipiente() {
return recipiente;
}
#XmlElement(name = "folder", defaultValue = "")
public final String getFolder() {
return folder;
}
#XmlElement(name = "creationtime")
#XmlJavaTypeAdapter(type = Date.class, value = JavaDateAdapter.class)
public final Date getDataCricao() {
return dataCricao;
}
#XmlElement(name = "senttime")
#XmlJavaTypeAdapter(type = Date.class, value = JavaDateAdapter.class)
public final Date getDataEnvio() {
return dataEnvio;
}
#XmlElement(name = "receivedtime")
#XmlJavaTypeAdapter(type = Date.class, value = JavaDateAdapter.class)
public final Date getDataRecebimento() {
return dataRecebimento;
}
#XmlElement(name = "state", required = true)
public final Integer getStatus() {
return status;
}
#XmlElement(name = "messagetype", required = true)
public final String getTipoMensagem() {
return tipoMensagem;
}
#XmlElement(name = "messagedata")
public final String getMensagem() {
return mensagem;
}
#XmlElement(name = "operatornames")
public final List<Protocolo> getProtocolos() {
return protocolos;
}
#XmlElement(name = "routes")
public final List<Route> getRotas() {
return rotas;
}
#XmlElement(name = "optionalfield")
#XmlElementWrapper(name = "optionalfields")
public List<Field> getOptionalFields() {
return optionalFields;
}
#XmlElement(name = "error")
public Error getError() {
return error;
}
/* omitted set methods */
}
The class represent the sendMessage response operation
/* omite desnecessary imports */
#XmlRootElement(name = "data")
#XmlAccessorType(XmlAccessType.PROPERTY)
public class SendMessageData extends Data {
private AcceptReport acceptReport;
#XmlElement(name = "acceptreport")
public AcceptReport getAcceptReport() {
return acceptReport;
}
#SuppressWarnings("unused")
public void setAcceptReport(AcceptReport acceptReport) {
this.acceptReport = acceptReport;
}
#Override
public String toString() {
return String.format("Report( %s )", this.acceptReport);
}
#XmlRootElement(name = "acceptreport")
public static class AcceptReport {
private Integer status;
private String statusMessage;
private String messageId;
private String originator;
private String recipient;
private String messageType;
private String messageData;
#XmlElement(name = "statuscode")
public Integer getStatus() {
return status;
}
#XmlElement(name = "statusmessage")
public String getStatusMessage() {
return statusMessage;
}
#XmlElement(name = "messageid")
public String getMessageId() {
return messageId;
}
#XmlElement(name = "originator")
public String getOriginator() {
return originator;
}
#XmlElement(name = "recipient")
public String getRecipient() {
return recipient;
}
#XmlElement(name = "messagetype")
public String getMessageType() {
return messageType;
}
#XmlElement(name = "messagedata")
public String getMessageData() {
return messageData;
}
/* set methods omited */
#Override
public String toString() {
return String.format("[ stats: %d, msgId: %s, msg: %s ]", this.status, this.messageId, this.messageData);
}
}
}
The xml 'data' section don't have anything to identifing 'what son of Data will be use in the ummarshaling operation?'
My test works fine in the marshalling operation but in the ummarshalling its broken because the jaxb don't identify the son of Data to use.
/* omite desnecessary imports */
public class Teste {
public static void main(String[] args) throws JAXBException {
JAXBContext ctx = JAXBContext.newInstance(
Data.class, GetAccountData.class,
GetInfoMessageData.class, Response.class, SendMessageData.class
);
Marshaller marshaller = ctx.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Response r = new Response();
r.setAction("getcredits");
GetAccountData data = new GetAccountData();
Account account = new GetAccountData.Account();
account.setUsername("aptalaser");
account.setBalance(12523L);
data.setAccount(Arrays.asList(account));
r.setData(data);
//in there is equal to first xml
marshaller.marshal(r, System.out);
Unmarshaller unmarshaller = ctx.createUnmarshaller();
Response resp = (Response) unmarshaller.unmarshal(new File("./get-credits.xml"));
//but in there the resp retrieved is to the third xml(and the Data field is null)
System.out.println(resp);
}
}
The question is: I need implement my own customized converter or i can make that with simple configurations like annotations?
Thanks for help.
For the purposes of unmarshalling you need to have a unique identifier that can be used to identify the appropriate subclass to instantiate.
#XmlElementRef
Since you are using #XmlElementRef this unique identifier should be the #XmlRootElement of the subclass, but you currently have all subclasses with #XmlRootElement(name="data").
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html
xsi:type Attribute
If you want to always leverage the data element then you need something else as the inheritance indicator. If you remove the #XmlElementRef annotation then JAXB will leverage the xsi:type attribute for this purpose.
http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html
Element Content
If neither of the above will work for you then you can leverage an XmlAdapter.
http://blog.bdoughan.com/2012/01/jaxb-and-inhertiance-using-xmladapter.html
UPDATE
Thank you #Blaise this help me so much, one more thing: i need process
this model, so i need implement different response class with
appropriate subclass field?
If you know which type of response you are receiving this is a valid approach. Then when you do the unmarshal you can specify the class you are unmarshalling. You will need to do this because the response root element corresponds to all the response classes you will make.
GetAccountDataResponse = unmarshaller.unmarshal(xml, GetAccountDataResponse.class).getValue();
If you don't know what type of response you are receiving and need to decide based on the content of the message then an XmlAdapter approach for handling inheritance will work.
http://blog.bdoughan.com/2012/01/jaxb-and-inhertiance-using-xmladapter.html

Categories

Resources