Using Gson and Abstract Classes - java

I'm trying to exchange messages between a client and a server using GSON.
The problem is the following:
I have this structure:
public class Message
{
private TypeOfContent type; // It's a enum
private Content content;
....
}
Then the object content can be a various set of Classes.
I found 2 tutorials here and here, but none of them solves the problem.
Edit1:
The class Message is this one:
public class Mensagem
{
private TipoMensagem type;
private Conteudo conteudo;
private Cliente autor;
private Cliente destino; // null -> to all(broadcast)
}
And Content is this one:
public class Conteudo
{
protected TipoConteudo typeConteudo;
protected String texto;
protected Posicao posicao;
public Conteudo(TipoConteudo typeConteudo, String texto, Posicao posicao)
{
this.texto = texto;
this.posicao = posicao;
this.typeConteudo = typeConteudo;
}
}
And an example of a extend class from conteudo is this one:
public class ConteudoTweet extends Conteudo
{
protected String pathImagem;
public ConteudoTweet(TipoConteudo typeConteudo, String tweet, Posicao location, String picturePath)
{
super(typeConteudo,tweet, location);
this.pathImagem = picturePath;
}
}
Finally what I do is like : "String strObject = new Gson().toJson(mensage);" which works but on deserialization it doesn't because it assumes always that it is from Content class

I finally solved it!
// GSON
GsonBuilder gsonBilder = new GsonBuilder();
gsonBilder.registerTypeAdapter(Conteudo.class, new InterfaceAdapter<Conteudo>());
gsonBilder.setPrettyPrinting();
Gson gson =gsonBilder.create();
String str2send = gson.toJson(message);
Mensagem msg_recv = gson.fromJson(str2send,Mensagem.class);
Note that: "registerTypeAdapter(AbstractClass.class, new InterfaceAdapter());"
by AbstractClass.class i mean the class that you are implementing in my case it was Conteúdo that could be ConteudoTweet or ConteudoUserSystem and so on...
The implementation of InterfaceAdapter is :
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
public class InterfaceAdapter<T>
implements JsonSerializer<T>, JsonDeserializer<T> {
#Override
public final JsonElement serialize(final T object, final Type interfaceType, final JsonSerializationContext context)
{
final JsonObject member = new JsonObject();
member.addProperty("type", object.getClass().getName());
member.add("data", context.serialize(object));
return member;
}
#Override
public final T deserialize(final JsonElement elem, final Type interfaceType, final JsonDeserializationContext context)
throws JsonParseException
{
final JsonObject member = (JsonObject) elem;
final JsonElement typeString = get(member, "type");
final JsonElement data = get(member, "data");
final Type actualType = typeForName(typeString);
return context.deserialize(data, actualType);
}
private Type typeForName(final JsonElement typeElem)
{
try
{
return Class.forName(typeElem.getAsString());
}
catch (ClassNotFoundException e)
{
throw new JsonParseException(e);
}
}
private JsonElement get(final JsonObject wrapper, final String memberName)
{
final JsonElement elem = wrapper.get(memberName);
if (elem == null)
{
throw new JsonParseException(
"no '" + memberName + "' member found in json file.");
}
return elem;
}
}
And this InterfaceAdapter is generic so it should work in general...
That's it!

You should take a look on a similar question I've answered here :
https://stackoverflow.com/a/22081826/3315914
You need to use Gson's RuntimeTypeAdapterFactory
And register the base class and all child classes to make it work.

Here is my take on Sub type serialization. (github repo here)
// GsonSerializer.java
package com.rathnas.main;
import java.lang.reflect.Type;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.rathnas.vo.Thing;
import com.rathnas.vo.sub.Animal;
import com.rathnas.vo.sub.Bird;
import com.rathnas.vo.sub.nested.Barking;
import com.rathnas.vo.sub.nested.Chirping;
import com.rathnas.vo.sub.nested.NoiseType;
public class GsonSerializer {
public static void main(String[] args) {
GsonBuilder builder = new GsonBuilder().registerTypeAdapter(Thing.class, new ThingSerializer<Thing>()).registerTypeAdapter(NoiseType.class, new ThingSerializer<NoiseType>());
builder.setPrettyPrinting();
Gson gson = builder.create();
Animal thing = God.createDog();
String tmp = gson.toJson(thing, Thing.class); // Note: StackoverflowError, if you do gson.toJson(thing)
System.out.println("Ser Dog: " + tmp);
System.out.println("Des Dog: " + gson.fromJson(tmp, Thing.class));
Bird thing2 = God.createBird();
tmp = gson.toJson(thing2, Thing.class);
System.out.println("\n\n\nSer Bird: " + tmp);
System.out.println("Des Bird: " + gson.fromJson(tmp, Thing.class));
}
}
class ThingSerializer<T> implements JsonSerializer<T>, JsonDeserializer<T> {
private static final String TYPE = "type";
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObj = json.getAsJsonObject();
String className = jsonObj.get(TYPE).getAsString();
try {
return context.deserialize(json, Class.forName(className));
} catch (ClassNotFoundException e) {
throw new JsonParseException(e);
}
}
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
JsonElement jsonEle = context.serialize(src, src.getClass());
jsonEle.getAsJsonObject().addProperty(TYPE, src.getClass().getCanonicalName());
return jsonEle;
}
}
class God {
public static Animal createDog() {
Animal thing = new Animal();
thing.setName("Dog");
thing.setLegs(4);
thing.setWings(0);
thing.setNoise(new Barking());
return thing;
}
public static Bird createBird() {
Bird thing = new Bird();
thing.setName("Bird");
thing.setLegs(1);
thing.setWings(2);
thing.setNoise(new Chirping());
return thing;
}
}
// Thing.java
public abstract class Thing {
private String name;
private NoiseType noise;
..
}
// Animal.java
public class Animal extends Thing implements iThing {
private Integer legs;
private Integer wings;
..
}
// Bird.java
public class Bird extends Thing implements iThing {
private Integer legs;
private Integer wings;
..
}
// NoiseType.java
public abstract class NoiseType {..}
// Chirping.java
public class Chirping extends NoiseType {..}
// Barking.java
public class Barking extends NoiseType {..}
Output
Ser Dog: {
"legs": 4,
"wings": 0,
"name": "Dog",
"noise": {
"noise": "barking",
"type": "com.rathnas.vo.sub.nested.Barking"
},
"type": "com.rathnas.vo.sub.Animal"
}
Des Dog: Animal [legs=4, wings=0, noise=NestedAbstractClass [noise=barking]]
Ser Bird: {
"legs": 1,
"wings": 2,
"name": "Bird",
"noise": {
"noise": "chirping",
"type": "com.rathnas.vo.sub.nested.Chirping"
},
"type": "com.rathnas.vo.sub.Bird"
}
Des Bird: Bird [legs=1, wings=2, noise=NestedAbstractClass [noise=chirping]]

Related

FasterXml - JsonSerializer HashMap

I'm using jackson-databind version 2.12.3 to serialize the return of an object that should return like this:
{
"field1":"value1",
"field2":"value2",
"links":{
"field":{
"href":"/link"
},
"test":{
"href":"/test"
}
}
}
My classes are these:
public class HrefType {
private String href = null;
...
}
public class Link extends HashMap<String, HrefType> {
private HrefType field = null;
...
}
public class MyObject {
private String field1 = null;
private String field2 = null;
private Link links = null;
...
}
The return is myObject:
MyObject myObject = new MyObject();
myObject.setField1("value1");
myObject.setField2("value2");
Link link = new Link();
link.setField(new HrefType().href("/link"));
link.put("test",new HrefType().href("/test"));
myObject.setLinks(link);
However with the default ObjectMapper the "link.setField" is ignored and the returned json is:
{
"field1":"value1",
"field2":"value2",
"links":{
"test":{
"href":"/test"
}
}
}
I tried doing some tests with JsonSerializer but couldn't do something generic for all classes that extend HashMap (these classes are generated from BerlinGroup's PSD2 YAML, so I wouldn't want to change the generated class).
Is there a generic way to do it, or should I make a serialize class for each class that extends the HashMap?
Composition
First of all, I suggest you use composition instead of inheritance in this particular case. Your code will look like the next:
private class Link {
private final HrefType field;
private final HashMap<String, HrefType> test;
public Link(HrefType field) {
this.field = field;
}
public HrefType getField() {
return field;
}
public HashMap<String, HrefType> getTest() {
return test;
}
}
And serialization will work fine, as expected.
Serializer
But in case, if you can't change the original code, you might to write your own StdSerializer. For example:
private class LinkSerializer extends StdSerializer<Link> {
public LinkSerializer() {
super(Link.class);
}
#Override
public void serialize(Link link, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
final HrefType field = link.getField();
jsonGenerator.writeObjectField("field", field);
jsonGenerator.writeObjectField("test", new HashMap<>(link));
jsonGenerator.writeEndObject();
}
}
And declare it over your Link class:
#JsonSerialize(using = LinkSerializer.class)
private static class Link extends HashMap<String, HrefType> {
private final HrefType field;
public Link(HrefType field) {
this.field = field;
}
public HrefType getField() {
return field;
}
}
based on this answer I developed this generic method of making for all objects that extend a Map:
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;
import org.springframework.util.ReflectionUtils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class MyClassSerializer extends JsonSerializer<Object> {
private final JsonSerializer<Object> defaultSerializer;
public MyClassSerializer(JsonSerializer<Object> defaultSerializer) {
this.defaultSerializer = (defaultSerializer);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
public void serialize(Object src, JsonGenerator gen, SerializerProvider provider) throws IOException {
Field[] fields = src.getClass().getDeclaredFields();
for (Field field : fields) {
try {
boolean fieldAccessible = field.isAccessible();
field.setAccessible(true);
Object object = ReflectionUtils.getField(field, src);
if (object != null && object instanceof Map) {
Field[] fieldsMap = object.getClass().getDeclaredFields();
Map map = (Map) object;
for (Field fieldMap : fieldsMap) {
boolean fieldMapAccessible = fieldMap.isAccessible();
fieldMap.setAccessible(true);
Object fieldObject = ReflectionUtils.getField(fieldMap, object);
if (fieldObject != null) {
map.put(fieldMap.getName(), fieldObject);
}
fieldMap.setAccessible(fieldMapAccessible);
}
}
field.setAccessible(fieldAccessible);
} catch (Exception e) {
e.printStackTrace();
}
}
defaultSerializer.serialize(src, gen, provider);
}
#Override
public Class<Object> handledType() {
return Object.class;
}
}
which goes through all fields, when I find one that extends from a Map I go through all the fields of this one and add it to the Map ignoring the object's fields, so the Serializer works perfectly.
EDIT: to Deserializer properly I do this:
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;
import org.springframework.util.ReflectionUtils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
#SuppressWarnings("rawtypes")
public class MyClassDeserializer extends JsonDeserializer implements ResolvableDeserializer {
private JsonDeserializer defaultDeserializer;
protected MyClassDeserializer(JsonDeserializer deserializer) {
this.defaultDeserializer = deserializer;
}
#SuppressWarnings("unchecked")
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Object obj = defaultDeserializer.deserialize(p, ctxt);
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
try {
boolean fieldAccessible = field.isAccessible();
field.setAccessible(true);
Object object = ReflectionUtils.getField(field, obj);
if (object != null && object instanceof Map) {
Field[] fieldsMap = object.getClass().getDeclaredFields();
Map map = (Map) object;
for (Object key : map.keySet()) {
for (Field fieldMap : fieldsMap) {
if (fieldMap.getName().equals((String) key)) {
if (fieldMap.getName().equalsIgnoreCase("serialVersionUID")) {
continue;
}
boolean fieldMapAccessible = fieldMap.isAccessible();
fieldMap.setAccessible(true);
Object fieldObject = ReflectionUtils.getField(fieldMap, object);
if (fieldObject == null) {
fieldMap.set(object, map.get(key));
map.replace(key, null);
}
fieldMap.setAccessible(fieldMapAccessible);
}
}
}
Object[] keys = map.keySet().toArray();
for (int i = 0; i < keys.length; i++) {
if(map.get(keys[i])==null) {
map.remove(keys[i]);
}
}
}
field.setAccessible(fieldAccessible);
} catch (Exception e) {
e.printStackTrace();
}
}
return obj;
}
#Override
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}

Different fields with same JsonProperty attribute

Is it possible to have something like below while serializing a JSON in the same class
#JsonProperty("stats")
private StatsDetails statsDetails
#JsonProperty("stats")
private List<StatsDetails> statsDetailsList
so i can have either statsDetails or statsDetailsList only one of these being included while forming a json.
I also have a separate JsonMapper code that transforms this pojo data into a json which i haven't included here.
You cannot do that. It will throw JsonMappingException jackson cannot know which of the fields are you referring to. You can try it by yourself with the following code:
POJOClass:
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import java.util.List;
public class POJOClass {
public POJOClass(String object) {
this.object = object;
}
public POJOClass(List<String> objectList) {
this.objectList = objectList;
}
#JsonProperty("object")
public String object;
#JsonProperty("object")
public List<String> objectList;
#JsonGetter("object")
public String getObject() {
return object;
}
#JsonGetter("object")
public List<String> getObjectList() {
return objectList;
}
#JsonSetter("object")
public void setObject(String object) {
this.object = object;
}
#JsonSetter("object")
public void setObjectList(List<String> objectList) {
this.objectList = objectList;
}
}
Main class:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class MainClass {
public static void main(String[] args) {
String text = "f";
List<String> list = Arrays.asList("a", "b", "c");
ObjectMapper mapper = new ObjectMapper();
try {
String json = mapper.writeValueAsString(new POJOClass(text));
String listJson = mapper.writeValueAsString(new POJOClass(list));
System.out.println("json=" + json);
System.out.println("listJson=" + listJson);
} catch (IOException e) {
e.printStackTrace();
}
}
}
The output:
com.fasterxml.jackson.databind.JsonMappingException: Multiple fields representing property "object": POJOClass#object vs POJOClass#objectList

Prevent Jackson XML mapper from adding wstxns to namespaces

When serialising objects to XML and specifying namespaces for properties using
#JacksonXmlRootElement(namespace = "http://...")
Jackson will append or prepend ´wstxns1´ to the namespace. For example, say we have these classes:
VtexSkuAttributeValues.java
#JacksonXmlRootElement(localName = "listStockKeepingUnitName")
public class VtexSkuAttributeValues {
#JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
#JacksonXmlElementWrapper(useWrapping = false)
private VtexSkuAttributeValue[] stockKeepingUnitFieldNameDTO;
public VtexSkuAttributeValue[] getStockKeepingUnitFieldNameDTO() {
return stockKeepingUnitFieldNameDTO;
}
public void setValues(VtexSkuAttributeValue[] values) {
this.stockKeepingUnitFieldNameDTO = values;
}
}
VtexSkuAttributeValue.java
#JacksonXmlRootElement(localName = "StockKeepingUnitFieldNameDTO", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
public class VtexSkuAttributeValue {
private String fieldName;
private FieldValues fieldValues;
private int idSku;
public int getIdSku() {
return idSku;
}
public String getFieldName() {
return fieldName;
}
public FieldValues getFieldValues() {
return fieldValues;
}
public void setIdSku(int idSku) {
this.idSku = idSku;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public void setFieldValues(FieldValues fieldValues) {
this.fieldValues = fieldValues;
}
#JacksonXmlRootElement(localName = "fieldValues", namespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts")
public static class FieldValues {
#JacksonXmlProperty(namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")
#JacksonXmlElementWrapper(useWrapping = false)
public String[] string;
public String[] getString() {
return string;
}
public void setValues(String[] values) {
this.string = values;
}
}
}
I then use the XmlMapper to serialise and get:
<listStockKeepingUnitName>
<wstxns1:StockKeepingUnitFieldNameDTO xmlns:wstxns1="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>talle</fieldName>
<fieldValues>
<wstxns2:string xmlns:wstxns2="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</wstxns2:string>
</fieldValues>
<idSku>258645</idSku>
</wstxns1:StockKeepingUnitFieldNameDTO>
<wstxns3:StockKeepingUnitFieldNameDTO xmlns:wstxns3="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>color</fieldName>
<fieldValues>
<wstxns4:string xmlns:wstxns4="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6244</wstxns4:string>
</fieldValues>
<idSku>258645</idSku>
</wstxns3:StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>
Even though this is valid XML, the web service I'm working with doesn't accept it. I debugged it and it's due to the wstxns properties in the tags that Jackson adds for some reason.
Is there a way to prevent Jackson from adding that to the tags. The only workaround I could come up with is performing a string.replaceAll on the resulting XML but it's obviously not ideal.
To write XML Jackson uses javax.xml.stream.XMLStreamWriter. You can configure instance of that class and define your own prefixes for namespaces and set default one if needed. To do that we need to extend com.fasterxml.jackson.dataformat.xml.XmlFactory class and override a method which creates XMLStreamWriter instance. Example implementation could look like below:
class NamespaceXmlFactory extends XmlFactory {
private final String defaultNamespace;
private final Map<String, String> prefix2Namespace;
public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
}
#Override
protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
XMLStreamWriter writer = super._createXmlWriter(ctxt, w);
try {
writer.setDefaultNamespace(defaultNamespace);
for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
writer.setPrefix(e.getKey(), e.getValue());
}
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, null);
}
return writer;
}
}
You can use it as below:
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
public class XmlMapperApp {
public static void main(String[] args) throws Exception {
String defaultNamespace = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";
Map<String, String> otherNamespaces = Collections.singletonMap("a", "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
XmlMapper xmlMapper = new XmlMapper(new NamespaceXmlFactory(defaultNamespace, otherNamespaces));
xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
System.out.println(xmlMapper.writeValueAsString(new VtexSkuAttributeValues()));
}
}
In VtexSkuAttributeValues class you can declare:
public static final String DEF_NMS = "http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts";
and use it for every class and field where it should be used as default namespace. For example:
#JacksonXmlProperty(localName = "StockKeepingUnitFieldNameDTO", namespace = DEF_NMS)
For properties, for which you do not want to change name you can use:
#JacksonXmlProperty(namespace = VtexSkuAttributeValues.DEF_NMS)
Above code prints for some random data:
<listStockKeepingUnitName>
<StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>Name1</fieldName>
<fieldValues>
<a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
</fieldValues>
<idSku>123</idSku>
</StockKeepingUnitFieldNameDTO>
<StockKeepingUnitFieldNameDTO xmlns="http://schemas.datacontract.org/2004/07/Vtex.Commerce.WebApps.AdminWcfService.Contracts">
<fieldName>Name1</fieldName>
<fieldValues>
<a:string xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">6184</a:string>
</fieldValues>
<idSku>123</idSku>
</StockKeepingUnitFieldNameDTO>
</listStockKeepingUnitName>
If it is not what you want you can play with that code and try other methods which are available for you to configure this instance.
To create this example Jackson in version 2.9.9 was used.
This seems to be the missing piece. It allows you to set the prefix and namespace.
static class NamespaceXmlFactory extends XmlFactory {
private final String defaultNamespace;
private final Map<String, String> prefix2Namespace;
public NamespaceXmlFactory(String defaultNamespace, Map<String, String> prefix2Namespace) {
this.defaultNamespace = Objects.requireNonNull(defaultNamespace);
this.prefix2Namespace = Objects.requireNonNull(prefix2Namespace);
}
#Override
protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException {
XMLStreamWriter2 writer = (XMLStreamWriter2)super._createXmlWriter(ctxt, w);
try {
writer.setDefaultNamespace(defaultNamespace);
writer.setPrefix("xsi", "http://www.w3.org/2001/XMLSchema-instance");
for (Map.Entry<String, String> e : prefix2Namespace.entrySet()) {
writer.setPrefix(e.getKey(), e.getValue());
}
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, null);
}
return writer;
}
}
The only remaining issue I have is
#JacksonXmlProperty(localName = "#xsi.type", isAttribute = true, namespace = "http://www.w3.org/2001/XMLSchema-instance")
#JsonProperty("#xsi.type")
private String type;
Creates the following output:
Still trying to resolve how to make it be xsi:type="networkObjectGroupDTO" instead.

Deserialize json with boolean or object field

I use Retrofit and GSON and I'm getting Json like this:
{
"firstName": "John",
"lastName": "Doe",
"passporRf": {
"number": "996633",
"series": "1111",
"code": "66666"
}
}
And when user doenst have a passport - this fields is boolean with "false" value.
How to deserialize it correctly and get boolean value - false, when this field is boolean and get JSON object when its object.
I found a JSONDeserializer but i cant to use it correctly. Code is:
public class DocumentDeserializer implements JsonDeserializer<Passport> {
#Override
public Passport deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonPrimitive primitive = json.getAsJsonPrimitive();
if (primitive.isBoolean()) {
// What value do i have to return here?
}
return context.deserialize(json, Passport.class);
}
}
Don't use GSon nor any other binding library. Nothing can be bound to "either a boolean or an object", so don't try.
Use a JSON parser, and look at the JSON content to obtain the information you want. Possibly build a User object out of the information you can find.
Code is given below,
Sample.class
import com.google.gson.Gson;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
/**
* Class containing news information in form of POJO.
*/
public class Sample {
public String firstName;
public String lastName;
private List<SampleOne> passporRf;
public List<SampleOne> getPassporRf() {
return passporRf;
}
private void setPassporRf(List<SampleOne> passporRf) {
this.passporRf = passporRf;
}
// Using custom DeSerializer for "multimedia" object since the API is just "Great"
public static class SampleDetailsDeSerializer implements JsonDeserializer<Sample> {
#Override
public Sample deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Sample accountState = new Gson().fromJson(json, Sample.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("passporRf")) {
JsonElement elem = jsonObject.get("passporRf");
if (elem != null && !elem.isJsonNull()) {
if(elem.isJsonPrimitive()){
accountState.setPassporRf(null);
} else {
final List<SampleOne> passporRfList = new Gson().fromJson(elem.getAsJsonArray().toString()
, new TypeToken<List<SampleOne>>(){}.getType());
accountState.setPassporRf(passporRfList);
}
}
}
return accountState ;
}
}
}
SampleOne.class
public class SampleOne {
public String number;
public String series;
public String code;
}
Hope it may help you.

How to deserialize same name node into two different classes with XStream

I have a problem while deserializing an XML file.
My file is like:
<mission>
<branch>
<alternative uid="0" type="ALT_MONITOR"/>
<alternative uid="1" type="ALT_IF" condition="i==10"/>
</branch>
</mission>
I have a class called Alternative:
public abtract class Alternative {
#XStreamAsAttribute
public int uid;
#XStreamAsAttribute
public String type;
}
This class is extended by two other class:
#XStreamAlias("alternative")
public class AlternativeA extends Alternative {
}
#XStreamAlias("alternative")
public class AlternativeB extends Alternative {
#XStreamAsAttribute
public String condition;
}
And then i have an xStream converter :
public class AlternativeConverter extends ReflectionConverter {
public AlternativesConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
if (reader.getAttribute("condition") != null) {
AlternativeA alternativeA = new AlternativeA();
alternativeA.setUid(Integer.parseInt(reader.getAttribute("uid")));
alternativeA.setCondition(reader.getAttribute("condition"));
return super.doUnmarshal(alternativeA, reader, context);
}else {
AlternativeB alternativeB = new AlternativeB();
alternativeB.setUid(Integer.parseInt(reader.getAttribute("uid")));
return super.doUnmarshal(alternativeB, reader, context);
}
}
#SuppressWarnings("unchecked")
#Override
public boolean canConvert(Class clazz) {
return Alternative.class.isAssignableFrom(clazz);
}
}
But when i try to convert the xml to an object. When it reaches the alternative with a condition it throws an exception :
Cannot convert type AlternativeB to type AlternativeA
Do any of you have an idea or an int on what could cause that error ?
Thank you in advance.
Java:
package de.mosst.spielwiese;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.mapper.Mapper;
import lombok.Data;
public class XStreamMultiClassesTest {
#Test
public void smokeTest() {
InputStream file = XStreamMultiClassesTest.class.getResourceAsStream("XStreamMultiClassesTest.xml");
XStream xStream = new XStream();
xStream.ignoreUnknownElements();
xStream.processAnnotations(Mission.class);
xStream.processAnnotations(Alternative.class);
Converter converter = new AlternativeConverter(xStream.getMapper(), xStream.getReflectionProvider());
xStream.registerConverter(converter);
Mission mission = (Mission) xStream.fromXML(file);
System.out.println(mission);
mission.branch.forEach(a -> {
System.out.println(a.getClass());
if (a instanceof AlternativeA) {
System.out.println("- condition: " + ((AlternativeA) a).condition);
}
});
}
public class AlternativeConverter extends ReflectionConverter {
public AlternativeConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
#Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Alternative alternative = null;
if (reader.getAttribute("condition") != null) {
alternative = new AlternativeA();
((AlternativeA) alternative).condition = reader.getAttribute("condition");
} else {
alternative = new AlternativeB();
}
alternative.uid = Integer.parseInt(reader.getAttribute("uid"));
return super.doUnmarshal(alternative, reader, context);
}
#Override
public boolean canConvert(#SuppressWarnings("rawtypes") Class clazz) {
return Alternative.class.isAssignableFrom(clazz);
}
}
#XStreamAlias("mission")
#Data
class Mission {
public List<Alternative> branch = new ArrayList<>();
}
#XStreamAlias("alternative")
#Data
abstract class Alternative {
#XStreamAsAttribute
public int uid;
#XStreamAsAttribute
public String type;
}
class AlternativeA extends Alternative {
public String condition;
}
class AlternativeB extends Alternative {
}
}
XML:
<?xml version="1.0" encoding="UTF-8"?>
<mission>
<branch>
<alternative uid="0" type="ALT_MONITOR" />
<alternative uid="1" type="ALT_IF" condition="i==10" />
</branch>
</mission>

Categories