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
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>
I have a problem with serialization/deserialization with Jackson 1.9.13 (and Jackson 2.5.0) and fighting this now for a few days without any success.
My goal is to use #JsonAnyGetter & #JsonAnySetter, and I want to calculate dynamically if a object should be written to the output or not. I have a JSON definition which I serialize with the ObjectMapper (and check if the Object should be included or not), and then I convert the object back to a string.
I am using a "HidableSerializer" for this, which works fine during serialization, but not when converting the object back to a string.
Without #JsonAnySetter / -getter or the "HidableSerializer", everythings works fine, but not both together.
Why is this not working? And how can I solve the problem? Better approaches are welcome!
The stack trace looks like this:
Stack Trace
org.codehaus.jackson.map.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: ch.hasselba.Test["[anySetter]"])
null
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:218)
at org.codehaus.jackson.map.JsonMappingException.wrapWithPath(JsonMappingException.java:183)
at org.codehaus.jackson.map.ser.std.SerializerBase.wrapAndThrow(SerializerBase.java:140)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:158)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
at ch.hasselba.HidableSerializer.serialize(HidableSerializer.java:29)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:610)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256)
at org.codehaus.jackson.map.ObjectMapper._configAndWriteValue(ObjectMapper.java:2575)
at org.codehaus.jackson.map.ObjectMapper.writeValueAsString(ObjectMapper.java:2097)
at ch.hasselba.Demo.main(Demo.java:54)
Caused by: java.lang.NullPointerException
at org.codehaus.jackson.map.ser.std.MapSerializer.serializeFields(MapSerializer.java:243)
at org.codehaus.jackson.map.ser.AnyGetterWriter.getAndSerialize(AnyGetterWriter.java:41)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:154)
... 7 more
The Demo code
package ch.hasselba;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.introspect.BasicBeanDescription;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.ser.BeanSerializerModifier;
public class Demo {
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
// register the module
Version version = new Version(1, 0, 0, "SNAPSHOT");
mapper.registerModule(new SimpleModule("HidableModule", version) {
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#SuppressWarnings("unchecked")
#Override
public JsonSerializer<?> modifySerializer(SerializationConfig config, BasicBeanDescription desc,
JsonSerializer<?> serializer) {
if (IHidable.class.isAssignableFrom(desc.getBeanClass())) {
return new HidableSerializer<Object>((JsonSerializer<Object>) serializer);
}
return serializer;
}
});
}
});
// the data
String content = "{ \"foo\": \"bar\" }";
// build the Object
Test test = null;
try {
test = mapper.readValue(content, Test.class);
} catch (Exception e) {
e.printStackTrace();
}
// and now convert it back to a String
String data = null;
try {
data = mapper.writeValueAsString(test);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println( data );
}
}
Test class
package ch.hasselba;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.codehaus.jackson.annotate.JsonAnyGetter;
import org.codehaus.jackson.annotate.JsonAnySetter;
public class Test implements IHidable {
private Map<String, Object> others = new ConcurrentHashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> getOthers() {
return this.others;
}
#JsonAnySetter
public void addOther(final String name, final Object value) {
this.others.put(name, value);
}
#Override
public boolean isHidden() {
return false;
}
}
The Hidable Serializer
package ch.hasselba;
import java.io.IOException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
public class HidableSerializer<T> extends JsonSerializer<T> {
private JsonSerializer<T> defaultSerializer;
public HidableSerializer(JsonSerializer<T> serializer) {
defaultSerializer = serializer;
}
#Override
public void serialize(T value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
if( value instanceof IHidable ){
IHidable hidableValue = (IHidable) value;
if( hidableValue.isHidden() )
return;
}
defaultSerializer.serialize(value, jgen, provider);
}
}
IHidableInterface
package ch.hasselba;
public interface IHidable {
boolean isHidden();
}
The problem is that the defaultSerializer instance, you are using inside your HidableSerializer, is a ResolvableSerializer (BeanSerializer), but as you wrap it into JsonSerializer (HidableSerializer) in your modifySerializer() method, it's resolve() method is then never invoked and it fails to initialize properly.
If you try adding the following line to your HidableSerializer.serialize() method:
...
((ResolvableSerializer)defaultSerializer).resolve(provider);
defaultSerializer.serialize(value, jgen, provider);
...
it should do the trick.
If this works for you, a more permanent solution would be to make your HidableSerializer implement ResolvableSerializer itself and just delegate resolve() to the defaultSerializer, like this:
#Override
public void resolve(SerializerProvider serializerProvider) throws JsonMappingException {
if(defaultSerializer instanceof ResolvableSerializer) {
((ResolvableSerializer)defaultSerializer).resolve(serializerProvider);
}
}
I went through debugging process and found some code:
if (ser instanceof ResolvableSerializer) {
((ResolvableSerializer) ser).resolve(provider);
}
It actually instantiates key serializer which throws NPE in your case.
Modify your HidableSerializer and it will do the trick:
public class HidableSerializer<T> extends JsonSerializer<T> implements ResolvableSerializer {
private JsonSerializer<T> defaultSerializer;
public HidableSerializer(JsonSerializer<T> serializer) {
defaultSerializer = serializer;
}
#Override
public void serialize(T value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
if( value instanceof IHidable ){
IHidable hidableValue = (IHidable) value;
if( hidableValue.isHidden() )
return;
}
defaultSerializer.serialize(value, jgen, provider);
}
#Override
public void resolve(SerializerProvider provider) throws JsonMappingException {
((ResolvableSerializer)defaultSerializer).resolve(provider);
}
}
Suppose I am writing custom serialization for some class, but would like to process one of its field with default methods.
How to do that?
While serializing we have JsonGenerator#writeObjectField().
But what is corresponding method for deserialization?
Regard the code below:
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.io.IOException;
import java.util.Objects;
public class TryDelegate {
public static class MyOuterClassSerializer extends JsonSerializer<MyOuterClass> {
#Override
public void serialize(MyOuterClass value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeObjectField("inner", value.getInner());
gen.writeEndObject();
}
}
public static class MyOuterClassDeserializer extends JsonDeserializer<MyOuterClass> {
#Override
public MyOuterClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
MyOuterClass ans = new MyOuterClass();
JsonToken token;
token = p.getCurrentToken();
if( token != JsonToken.START_OBJECT ) {
throw new JsonParseException("Start object expected", p.getCurrentLocation());
}
if( !"inner".equals(p.nextFieldName() ) ) {
throw new JsonParseException("'inner; field expected", p.getCurrentLocation());
}
MyInnerClass inner = null;// how to desrialize inner from here with default processing???
ans.setInner(inner);
token = p.nextToken();
if( token != JsonToken.END_OBJECT ) {
throw new JsonParseException("End object expected", p.getCurrentLocation());
}
return ans;
}
}
public static class MyInnerClass {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
#Override
public String toString() {
return "{\"value\":" + value + "}";
}
}
#JsonDeserialize(using = MyOuterClassDeserializer.class)
#JsonSerialize(using = MyOuterClassSerializer.class)
public static class MyOuterClass {
private MyInnerClass inner;
public MyInnerClass getInner() {
return inner;
}
public void setInner(MyInnerClass inner) {
this.inner = inner;
}
#Override
public String toString() {
return "{\"inner\":" + Objects.toString(inner) + "}";
}
}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String string;
MyInnerClass inner = new MyInnerClass();
inner.setValue(12);
MyOuterClass outer = new MyOuterClass();
outer.setInner(inner);
string = mapper.writeValueAsString(outer);
System.out.println(string);
MyOuterClass outer2 = mapper.readValue(string, MyOuterClass.class);
System.out.println(outer2); // inner was not deserialized
}
}
How to implement MyOuterDeserializer?
The DeserializationContext offers these tools.
After checking the field name for "inner", move to the next token, the beginning of the JSON object and use the DeserializationContext to deserialize the JSON object into a MyInnerClass object.
if (!"inner".equals(p.nextFieldName())) {
throw new JsonParseException("'inner; field expected", p.getCurrentLocation());
}
p.nextToken(); // consumes the field name token
MyInnerClass inner = ctxt.readValue(p, MyInnerClass.class);
The javadoc states
Convenience method that may be used by composite or container
deserializers, for reading one-off values contained (for sequences, it
is more efficient to actually fetch deserializer once for the whole
collection).
Careful while using the DeserializationContext. Don't try to recursively deserialize types for which you have have registered custom deserializers.
I have the following code:
public static class A
{
public A() {}
private List<B> bs = new ArrayList<B>();
public List<B> getBs() {
return bs;
}
public void setBs(List<B> bs) {
this.bs = bs;
}
}
public static class B
{
B(String foo){this.foo=foo;}
private String foo;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
public static void main(String[] args) throws Exception {
Gson gson = new Gson();
A a = new A();
a.getBs().add(new B("bar"));
System.out.println(gson.toJson(a));
}
and as expected the output is:
{"bs":[{"foo":"bar"}]}
However, if I make A a subclass of HashMap:
public static class A extends HashMap
I get an empty set returned: {}
I have even tried:
System.out.println(gson.toJson(a, new TypeToken<A>(){}.getType()));
and:
System.out.println(gson.toJson(a, new TypeToken<HashMap>(){}.getType()));
Can someone tell me whether/how I can serialise this HashMap subclass using GSON?
Gson works with (default and custom) TypeAdapterFactory instances and the TypeAdapter objects they create to serialize/deserialize your objects.
It goes through the list of registered TypeAdapterFactory objects and picks the first one that can create an appropriate TypeAdapter for the type of the object your are providing. One of these TypeAdapterFactory objects, is one of type MapTypeAdapterFactory which creates a TypeAdapter (of type MapTypeAdapterFactory$Adapter) that serializes/deserializes based on the java.util.Map interface (keys/values). It does nothing about your custom sub type's fields.
If you want Gson to serialize your type as both a Map and a custom type, you will need to register either a custom TypeAdapter directly or a custom TypeAdapterFactory that creates TypeAdapter objects.
Here is the custom TypeAdapterFactory.
Test:
public static void main(String[] args) throws Exception{
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new RetainFieldMapFactory())
.create();
Foo f = gson.fromJson("{'key1':'value1','key2':'value2'}", Foo.class);
System.out.println("in map:\t" + f.toString());
System.out.println("f.key1:\t"+f.key1);
System.out.println("toJson:\t"+gson.toJson(f));
}
public static class Foo extends HashMap<String, String> {
private String key1;
}
Output:
in map: {key2=value2}
f.key1: value1
toJson: {"key2":"value2","key1":"value1"}
RetainFieldMapFactory.java:
/**
* Created by linfaxin on 2015/4/9 009.
* Email: linlinfaxin#163.com
*/
public class RetainFieldMapFactory implements TypeAdapterFactory {
FieldNamingPolicy fieldNamingPolicy = FieldNamingPolicy.IDENTITY;
ConstructorConstructor constructorConstructor = new ConstructorConstructor(Collections.<Type, InstanceCreator<?>>emptyMap());
MapTypeAdapterFactory defaultMapFactory = new MapTypeAdapterFactory(constructorConstructor, false);
ReflectiveFilterMapFieldFactory defaultObjectFactory = new ReflectiveFilterMapFieldFactory(constructorConstructor,
fieldNamingPolicy, Excluder.DEFAULT);
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> mapAdapter = defaultMapFactory.create(gson, type);
if(mapAdapter!=null){
return (TypeAdapter<T>) new RetainFieldMapAdapter(mapAdapter, defaultObjectFactory.create(gson, type));
}
return mapAdapter;
}
class RetainFieldMapAdapter extends TypeAdapter<Map<String, Object>>{
TypeAdapter<Map<String, Object>> mapAdapter;
ReflectiveTypeAdapterFactory.Adapter<Map<String, Object>> objectAdapter;
RetainFieldMapAdapter(TypeAdapter mapAdapter, ReflectiveTypeAdapterFactory.Adapter objectAdapter) {
this.mapAdapter = mapAdapter;
this.objectAdapter = objectAdapter;
}
#Override
public void write(final JsonWriter out, Map<String, Object> value) throws IOException {
//1.write object
StringWriter sw = new StringWriter();
objectAdapter.write(new JsonWriter(sw), value);
//2.convert object to a map
Map<String, Object> objectMap = mapAdapter.fromJson(sw.toString());
//3.overwrite fields in object to a copy map
value = new LinkedHashMap<String, Object>(value);
value.putAll(objectMap);
//4.write the copy map
mapAdapter.write(out, value);
}
#Override
public Map<String, Object> read(JsonReader in) throws IOException {
//1.create map, all key-value retain in map
Map<String, Object> map = mapAdapter.read(in);
//2.create object from created map
Map<String, Object> object = objectAdapter.fromJsonTree(mapAdapter.toJsonTree(map));
//3.remove fields in object from map
for(String field : objectAdapter.boundFields.keySet()){
map.remove(field);
}
//4.put map to object
object.putAll(map);
return object;
}
}
/**
* If class is extends from some custom map,
* class should implement this to avoid serialize custom map's fields
*/
public interface RetainFieldFlag {}
static class ReflectiveFilterMapFieldFactory extends ReflectiveTypeAdapterFactory{
public ReflectiveFilterMapFieldFactory(ConstructorConstructor constructorConstructor, FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
super(constructorConstructor, fieldNamingPolicy, excluder);
}
#Override
protected boolean shouldFindFieldInClass(Class willFindClass, Class<?> originalRaw) {
if(RetainFieldFlag.class.isAssignableFrom(originalRaw)){
return RetainFieldFlag.class.isAssignableFrom(willFindClass);
}else{
Class[] endClasses = new Class[]{Object.class, HashMap.class, LinkedHashMap.class,
LinkedTreeMap.class, Hashtable.class, TreeMap.class, ConcurrentHashMap.class,
IdentityHashMap.class, WeakHashMap.class, EnumMap.class};
for(Class c : endClasses){
if(willFindClass == c) return false;
}
}
return super.shouldFindFieldInClass(willFindClass, originalRaw);
}
}
/**
* below code copy from {#link com.google.gson.internal.bind.ReflectiveTypeAdapterFactory}
* (little modify, in source this class is final)
* Type adapter that reflects over the fields and methods of a class.
*/
static class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorConstructor constructorConstructor;
private final FieldNamingStrategy fieldNamingPolicy;
private final Excluder excluder;
public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
this.constructorConstructor = constructorConstructor;
this.fieldNamingPolicy = fieldNamingPolicy;
this.excluder = excluder;
}
public boolean excludeField(Field f, boolean serialize) {
return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
}
private String getFieldName(Field f) {
SerializedName serializedName = f.getAnnotation(SerializedName.class);
return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value();
}
public <T> Adapter<T> create(Gson gson, final TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
}
private ReflectiveTypeAdapterFactory.BoundField createBoundField(
final Gson context, final Field field, final String name,
final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
// special casing primitives here saves ~5% on Android...
return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
final TypeAdapter<?> typeAdapter = context.getAdapter(fieldType);
#SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
#Override void write(JsonWriter writer, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = field.get(value);
TypeAdapter t = new TypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
}
#Override void read(JsonReader reader, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = typeAdapter.read(reader);
if (fieldValue != null || !isPrimitive) {
field.set(value, fieldValue);
}
}
};
}
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
if (raw.isInterface()) {
return result;
}
Type declaredType = type.getType();
Class<?> originalRaw = type.getRawType();
while (shouldFindFieldInClass(raw, originalRaw)) {
Field[] fields = raw.getDeclaredFields();
for (Field field : fields) {
boolean serialize = excludeField(field, true);
boolean deserialize = excludeField(field, false);
if (!serialize && !deserialize) {
continue;
}
field.setAccessible(true);
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
BoundField boundField = createBoundField(context, field, getFieldName(field),
TypeToken.get(fieldType), serialize, deserialize);
BoundField previous = result.put(boundField.name, boundField);
if (previous != null) {
throw new IllegalArgumentException(declaredType
+ " declares multiple JSON fields named " + previous.name);
}
}
type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
raw = type.getRawType();
}
return result;
}
protected boolean shouldFindFieldInClass(Class willFindClass, Class<?> originalRaw){
return willFindClass != Object.class;
}
static abstract class BoundField {
final String name;
final boolean serialized;
final boolean deserialized;
protected BoundField(String name, boolean serialized, boolean deserialized) {
this.name = name;
this.serialized = serialized;
this.deserialized = deserialized;
}
abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException;
abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException;
}
public static final class Adapter<T> extends TypeAdapter<T> {
private final ObjectConstructor<T> constructor;
private final Map<String, BoundField> boundFields;
private Adapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
this.constructor = constructor;
this.boundFields = boundFields;
}
#Override public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
T instance = constructor.construct();
try {
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
BoundField field = boundFields.get(name);
if (field == null || !field.deserialized) {
in.skipValue();
} else {
field.read(in, instance);
}
}
} catch (IllegalStateException e) {
throw new JsonSyntaxException(e);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
in.endObject();
return instance;
}
#Override public void write(JsonWriter out, T value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.beginObject();
try {
for (BoundField boundField : boundFields.values()) {
if (boundField.serialized) {
out.name(boundField.name);
boundField.write(out, value);
}
}
} catch (IllegalAccessException e) {
throw new AssertionError();
}
out.endObject();
}
}
}
static class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
private final Gson context;
private final TypeAdapter<T> delegate;
private final Type type;
TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
this.context = context;
this.delegate = delegate;
this.type = type;
}
#Override
public T read(JsonReader in) throws IOException {
return delegate.read(in);
}
#SuppressWarnings({"rawtypes", "unchecked"})
#Override
public void write(JsonWriter out, T value) throws IOException {
// Order of preference for choosing type adapters
// First preference: a type adapter registered for the runtime type
// Second preference: a type adapter registered for the declared type
// Third preference: reflective type adapter for the runtime type (if it is a sub class of the declared type)
// Fourth preference: reflective type adapter for the declared type
TypeAdapter chosen = delegate;
Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
if (runtimeType != type) {
TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for the runtime type, so we will use that
chosen = runtimeTypeAdapter;
} else if (!(delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for Base class, so we prefer it over the
// reflective type adapter for the runtime type
chosen = delegate;
} else {
// Use the type adapter for runtime type
chosen = runtimeTypeAdapter;
}
}
chosen.write(out, value);
}
/**
* Finds a compatible runtime type if it is more specific
*/
private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
if (value != null
&& (type == Object.class || type instanceof TypeVariable<?> || type instanceof Class<?>)) {
type = value.getClass();
}
return type;
}
}
}