I'm running a simple experiment test below.
public class MyTest {
#Test
public void testing() {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(SubData.class, new SubDataImplInstanceCreator());
Gson gson = builder.create();
Dataclass data = new Dataclass();
data.key1 = "abc";
SubDataImpl subData = new SubDataImpl();
subData.hello = "ttt";
data.sub = subData;
String jsonValue = gson.toJson(data);
System.out.println(jsonValue);
Dataclass data2 = gson.fromJson(jsonValue, Dataclass.class);
System.out.println(gson.toJson(data2));
}
class Dataclass implements Serializable {
String key1;
SubData sub;
}
interface SubData {
String getHello();
}
class SubDataImpl implements SubData, Serializable {
String hello;
#Override
public String getHello() {
return hello;
}
}
public class SubDataImplInstanceCreator implements InstanceCreator<SubDataImpl> {
#Override
public SubDataImpl createInstance(Type type) {
return new SubDataImpl();
}
}
}
I'm expecting it to return
{"key1":"abc","sub":{"hello":"ttt"}}
{"key1":"abc","sub":{"hello":"ttt"}}
As they are essentially the same data that get serialized and deserialized.
However, when I run it, I got
{"key1":"abc","sub":{"hello":"ttt"}}
{"key1":"abc","sub":{}}
Why did I loose away my SubData value, after deserializing the Json String? Did I miss anything in my code?
It seems you have hit this bug , the suggested solution is to use a TypeAdapter for the interface.
Quick and dirty implementation (use it in place ofSubDataImplInstanceTypeAdapter)
public class SubDataImplInstanceTypeAdapter implements JsonDeserializer<SubDataImpl>, JsonSerializer<SubDataImpl> {
#Override
public SubDataImpl deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
SubDataImpl impl = new SubDataImpl();
JsonObject object = json.getAsJsonObject();
impl.setHello(object.get("hello").getAsString());
return impl;
}
#Override
public JsonElement serialize(SubDataImpl src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src);
}
}
I'm using the below
public class MyTest {
#Test
public void testing() {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(SubData.class, new SubDataTypeAdapter());
Gson gson = builder.create();
Dataclass data = new Dataclass();
data.key1 = "abc";
SubDataImpl subData = new SubDataImpl();
subData.hello = "ttt";
data.sub = subData;
String jsonValue = gson.toJson(data);
System.out.println(jsonValue);
Dataclass data2 = gson.fromJson(jsonValue, Dataclass.class);
System.out.println(gson.toJson(data2));
}
class SubDataTypeAdapter extends TypeAdapter<SubDataImpl> {
#Override
public void write(JsonWriter out, final SubDataImpl subData) throws IOException {
out.beginObject();
out.name("hello").value(subData.getHello());
out.endObject();
}
#Override
public SubDataImpl read(JsonReader in) throws IOException {
final SubDataImpl subData = new SubDataImpl();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "hello":
subData.hello = in.nextString();
break;
}
}
in.endObject();
return subData;
}
}
class Dataclass implements Serializable {
String key1;
SubData sub;
}
abstract class SubData {
abstract String getHello();
}
class SubDataImpl extends SubData implements Serializable {
String hello;
#Override
public String getHello() {
return hello;
}
}
}
Related
I have a GsonFactory declared like this:
public class GsonFactory {
public Gson create() {
return new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(MyInterface.class, new MyInstanceCreator())
.create();
}
private static final GsonFactory instance = new GsonFactory();
private GsonFactory() {
}
public static Gson getInstance(){
return instance.create();
}
}
I also have the InstanceCreator class:
public class MyInstanceCreator implements InstanceCreator<MyInterface> {
#Override
public StrategySymbol createInstance(Type type) {
return new MyInterfaceImpl();
}
}
class MyClass {
private List<MyInterface> myList;
public List<MyInterface> getMyList() { return myList; }
public void setMyList(List<MyInterface> myList) { this.myList = myList; }
}
interface MyInterface {
Long getValue1();
void setValue1(Long value1);
}
class MyInterfaceImpl implements MyInterface {
private Long value1;
#Override
public Long getValue1() {
return value1;
}
#Override
public void setValue1(Long value1) {
this.value1 = value1
}
}
This code seems well implemented, but if I try to parse a JSON with value1:
MyClass obj = GsonFactory.getInstance().fromJson("{'myList': [{'value1':8}]}", MyClass.class);
The Object is returned with an instance of MyInterfaceImpl, but the field value1 is always null. As I could see, it seems like Gson looks for the fields in the interface (none) and not in the class implementing the interface.
Does anyone know how to solve this?
InstanceCreator can only create instances of a class that does not define a no-args constructor. It does not handle deserialization. Gson still won't be able to figure out the concrete data type of the object.
Solution. Custom deserializer for interface
You need to define a custom deserializer for your interface and register this new type adapter in factory.
public class MyInterfaceDeserializer implements JsonDeserializer<MyInterface> {
#Override
public MyInterface deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return jsonDeserializationContext.deserialize(jsonElement, MyInterfaceImpl.class);
}
}
public class GsonFactory {
public Gson create() {
return new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(MyInterface.class, new MyInterfaceDeserializer())
.create();
}
}
OR Generic deserializer
public class InterfaceDeserializer<T, E> implements JsonDeserializer<T> {
private final Class<E> implementationClass;
public InterfaceDeserializer(Class<E> implementationClass) {
this.implementationClass = implementationClass;
}
#Override
public T deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return jsonDeserializationContext.deserialize(jsonElement, implementationClass);
}
}
public class GsonFactory {
public Gson create() {
return new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(MyInterface.class, new InterfaceDeserializer<MyInterface, MyInterfaceImpl>(MyInterfaceImpl.class))
.create();
}
OR
public class MyInterfaceDeserializer implements JsonDeserializer<MyInterface> {
#Override
public MyInterface deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
MyInterface myInterface = new MyInterfaceImpl();
JsonObject jObject = jsonElement.getAsJsonObject();
myInterface.setValue1(jObject.get("value1").getAsLong());
return myInterface;
}
}
public class GsonFactory {
public Gson create() {
return new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(MyInterface.class, new MyInterfaceDeserializer())
.create();
}
}
Another option
Use RuntimeTypeAdapterFactory but this solution requires an extra type property in json to define the exact subtype, see the documentation.
Example:
public Gson create() {
RuntimeTypeAdapterFactory<MyInterface> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory.of(MyInterface.class).registerSubtype(MyInterfaceImpl.class, "MyInterfaceImpl");
return new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapterFactory(runtimeTypeAdapterFactory)
.create();
}
JSON must contain type field:
{
myList: [
{
value1: 8,
type: MyInterfaceImpl
}
]
}
Please note gson-extras library required for that option. This artifact is located at CronApp repository.
I succeeded to parse my object to a JSON array string :
public class MyClass extends JsonElement {
private JsonArray array;
public MyClass(String name, String role, String title) {
this.array = new JsonArray();
this.array.add(Objects.requireNonNull(name));
this.array.add(Objects.requireNonNull(role));
this.array.add(Objects.requireNonNull(title));
}
#Override
public JsonElement deepCopy() {
return array.deepCopy();
}
#Override
public boolean isJsonArray() {
return true;
}
#Override
public JsonArray getAsJsonArray() {
return array;
}
}
Gson gson = new Gson();
MyClass obj = new MyClass("ab", "cd", "ef");
System.out.println(gson.toJson(obj)); // "["ab", "cd", "ef"]"
How can we do the opposite ?
MyClass obj2 = gson.fromJson("[\"ab\", \"cd\", \"ef\"]", MyClass.class);
I got :
Exception in thread "main" com.google.gson.JsonSyntaxException: Expected a MyClass but was com.google.gson.JsonArray
I tried :
public class MyAdapter extends TypeAdapter<MyClass> {
#Override
public void write(JsonWriter out, CommunityTeamMember value) throws IOException {
TypeAdapters.JSON_ELEMENT.write(out, value);
}
#Override
public MyClass read(JsonReader in) throws IOException {
JsonArray array = TypeAdapters.JSON_ELEMENT.read(in).getAsJsonArray();
return new MyClass(array.get(0).getAsString(),array.get(1).getAsString(),array.get(2).getAsString());
}
}
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyClass.class, new MyAdapter());
Gson gson = builder.create();
You've done some trickery by making your object behave as a JsonArray to the serializer, so you get a JsonArray back, but the deserializer only knows to put it into a JsonArray. To get back an instance of the class you had, you can add a constructor that takes a JsonArray
class MyClass extends JsonElement {
private JsonArray array;
public MyClass(String name, String role, String title) {
this.array = new JsonArray();
this.array.add(Objects.requireNonNull(name));
this.array.add(Objects.requireNonNull(role));
this.array.add(Objects.requireNonNull(title));
}
public MyClass(JsonArray jsonArray) {
this.array = jsonArray;
}
#Override
public JsonElement deepCopy() {
return array.deepCopy();
}
#Override
public boolean isJsonArray() {
return true;
}
#Override
public JsonArray getAsJsonArray() {
return array;
}
}
public class Main {
public static void main(String[] args) {
Gson gson = new Gson();
MyClass obj = new MyClass("ab", "cd", "ef");
String serialized = gson.toJson(obj);
System.out.println(serialized); // "["ab", "cd", "ef"]"
MyClass obj2 = new MyClass(JsonParser.parseString(serialized).getAsJsonArray());
System.out.println(obj2.getAsJsonArray()); //["ab","cd","ef"]
}
}
I have this Java class:
class Car {
int mileage;
int id;
}
When I tell gson to serialize it, it of course serializes it to:
{
"mileage": 123,
"id": 12345678
}
But what if I want to serialize it to:
{
"mileage": "123",
"id": "12345678"
}
Assuming changing my members from int to String, is not an option, is there a way to tell gson to serialize those int members as strings to the json file?
There are likely many ways to achieve what you desire.
I will share two ways.
FIRST - Using Custom Serialization
SECOND - Using JsonAdapter Annotation - More Simple
Using a custom serialization
public static class CarSerializer implements JsonSerializer<Car> {
public JsonElement serialize(final Car car, final Type type, final JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("mileage", new JsonPrimitive(Integer.toString(car.getMileage())));
result.add("id", new JsonPrimitive(Integer.toString(car.getId())));
return result;
}
}
To call this, simply adapt your code or use the following code with a constructor
Car c = new Car(123, 123456789);
com.google.gson.Gson gson = new
GsonBuilder().registerTypeAdapter(Car.class, new CarSerializer())
.create();
System.out.println(gson.toJson(c));
The output should be
{"mileage":"123","id":"12345678"}
Full Code for Example 1:
public class SerializationTest {
public static class Car {
public int mileage;
public int id;
public Car(final int mileage, final int id) {
this.mileage = mileage;
this.id = id;
}
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
public String getMileage() {
return mileage;
}
public void setMileage(final String mileage) {
this.mileage = mileage;
}
}
public static class CarSerializer implements JsonSerializer<Car> {
public JsonElement serialize(final Car car, final Type type, final JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("mileage", new JsonPrimitive(Integer.toString(car.getMileage())));
result.add("id", new JsonPrimitive(Integer.toString(car.getId())));
return result;
}
}
public static void main(final String[] args) {
Car c = new Car(123, 123456789);
com.google.gson.Gson gson = new
GsonBuilder().registerTypeAdapter(Car.class, new CarSerializer())
.create();
System.out.println(gson.toJson(c));
}
}
Using a #JsonAdapter annotation
Use the JsonAdapter Annotation on the Car class
#JsonAdapter(CarAdapter.class)
public class Car {
public int mileage;
public int id;
}
Create the Custom Adapter
public class CarAdapter extends TypeAdapter<Car> {
#Override
public void write(JsonWriter writer, Car car) throws IOException {
writer.beginObject();
writer.name("mileage").value(car.getMileage());
writer.name("id").value(car.getId());
writer.endObject();
}
#Override
public Car read(JsonReader in) throws IOException {
// do something you need
return null;
}
}
To serialize, using this method, use something like this
Car c = new Car(123, 123456789);
Gson gson = new Gson();
String result = gson.toJson(c);
Printing result in this case should output
{"mileage":"123","id":"12345678"}
You may try it this way:
new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.registerTypeAdapter(Integer.class, (JsonSerializer<Integer>)
(integer, type, jsonSerializationContext) -> new
JsonPrimitive(String.valueOf(integer)))
.excludeFieldsWithoutExposeAnnotation().create();
To be clear, let introduse some model:
interface A {
boolean isSomeCase();
}
class AAdapter implements JsonSerializer<A> {
public JsonElement serialize(A src, Type typeOfSrc, JsonSerializationContext context) {
if (src.isSomeCase()) {
/* some logic */
return result;
} else {
JsonObject json = new JsonObject();
JsonElement valueJson = <???>; // TODO serialize src like POJO
json.add(src.getClass().getSimpleName(), valueJson);
return json;
}
}
}
Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(A.class. new AAdapter())
.create();
How it is possible to serealize some instance of A, which isSomeCase() = false, like any other object, that is serialized by ReflectiveTypeAdapterFactory.Adapter.
You can write a custom TypeAdapterFactory and handle incoming object's isSomeCase() result in its TypeAdapter's write() method and apply your logic there:
public class ATypeAdapterFactory implements TypeAdapterFactory {
public TypeAdapter<A> create(Gson gson, TypeToken type) {
if (!A.class.isAssignableFrom(type.getRawType())) {
// Check if incoming raw type is an instance of A interface
return null;
}
final TypeAdapter<A> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<A>() {
#Override
public void write(JsonWriter out, A value) throws IOException {
if(value.isSomeCase()) {
// your custom logic here
out.beginObject();
out.name("x").value(0);
out.endObject();
} else {
// default serialization here
delegate.write(out, value);
}
}
#Override
public A read(JsonReader in) throws IOException {
return delegate.read(in);
}
};
}
}
Test:
final GsonBuilder gsonBuilder = new GsonBuilder();
// Register custom type adapter factory
gsonBuilder.registerTypeAdapterFactory(new ATypeAdapterFactory());
final Gson gson = gsonBuilder.create();
A aSomeCaseTrue = new AImpl(true);
System.out.print("aSomeCaseTrue:" + gson.toJson(aSomeCaseTrue));
// writes; aSomeCaseTrue:{"x":0}
A aSomeCaseFalse = new AImpl(false);
System.out.print("aSomeCaseFalse:" + gson.toJson(aSomeCaseFalse););
// writes; aSomeCaseFalse:{"someCase":false}
Extras:
1) Your interface:
interface A {
boolean isSomeCase();
}
2) A sample class which implements your sample interface:
class AImpl implements A {
boolean someCase;
public AImpl(boolean value) {
this.someCase = value;
}
#Override
public boolean isSomeCase() {
return someCase;
}
}
I am trying to use GraphAdapterBuilder which is an extra to the GSON library to serialize an object with cyclic references. It works great for class but fails when trying to deserialize an interface.
To deserialize interface( which GSON doesn't do by default ) I am using PropertyBasedInterfaceMarshal or InterfaceAdapter. These are registered as custom type adapters for the interfaces.
When using ether above both fail to deserialize the interface as they are only passed the graph id like "0x4" as generated by GraphAdapterBuilder. This is passed as the JsonElement in the deserializer. Obviously there is nothing that can be done with this id from within the deserializer.
Shouldn't these be caught by the GraphAdapterBuilder instead of trying to be deserialized? I have not been able to get around this, is this a bug with GraphAdapterBuilder or is there a way to get around this?
Ok, this is a (working) stub for a solution. It's too late in Italy, to make it nicer.
You need a delegate function like this
package com.google.gson.graph;
/**
* #author Giacomo Tesio
*/
public interface GenericFunction<Domain, Codomain> {
Codomain map(Domain domain);
}
a TypeAdapterFactory like this:
package com.google.gson.graph;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* #author Giacomo Tesio
*/
public class InterfaceAdapterFactory implements TypeAdapterFactory {
final Map<String, GenericFunction<Gson, TypeAdapter<?>>> adapters;
private final Class<?> commonInterface;
public InterfaceAdapterFactory(Class<?> commonInterface, Class<?>[] concreteClasses)
{
this.commonInterface = commonInterface;
this.adapters = new HashMap<String, GenericFunction<Gson, TypeAdapter<?>>>();
final TypeAdapterFactory me = this;
for(int i = 0; i < concreteClasses.length; ++i)
{
final Class<?> clazz = concreteClasses[i];
this.adapters.put(clazz.getName(), new GenericFunction<Gson, TypeAdapter<?>>(){
public TypeAdapter<?> map(Gson gson) {
TypeToken<?> type = TypeToken.get(clazz);
return gson.getDelegateAdapter(me, type);
}
});
}
}
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
if(!this.commonInterface.isAssignableFrom(type.getRawType())
&& !this.commonInterface.equals(type.getRawType()))
{
return delegate;
}
final TypeToken<T> typeToken = type;
final Gson globalGson = gson;
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
out.beginObject();
out.name("#t");
out.value(value.getClass().getName());
out.name("#v");
delegate.write(out, value);
out.endObject();
}
#SuppressWarnings({"unchecked"})
public T read(JsonReader in) throws IOException {
JsonToken peekToken = in.peek();
if(peekToken == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginObject();
String dummy = in.nextName();
String typeName = in.nextString();
dummy = in.nextName();
TypeAdapter<?> specificDelegate = adapters.get(typeName).map(globalGson);
T result = (T)specificDelegate.read(in);
in.endObject();
return result;
}
};
}
}
a pair of tests like these
public final class InterfaceAdapterFactoryTest extends TestCase {
public void testInterfaceSerialization1(){
SampleInterface first = new SampleImplementation1(10);
SampleInterfaceContainer toSerialize = new SampleInterfaceContainer("container", first);
GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder()
.addType(SampleInterfaceContainer.class)
.addType(SampleImplementation1.class)
.addType(SampleImplementation2.class)
.registerOn(gsonBuilder);
gsonBuilder.registerTypeAdapterFactory(new InterfaceAdapterFactory(
SampleInterface.class, new Class<?>[] { SampleImplementation1.class, SampleImplementation2.class }
));
Gson gson = gsonBuilder.create();
String json = gson.toJson(toSerialize);
System.out.println(json);
SampleInterfaceContainer deserialized = gson.fromJson(json, SampleInterfaceContainer.class);
assertNotNull(deserialized);
assertEquals(toSerialize.getName(), deserialized.getName());
assertEquals(toSerialize.getContent().getNumber(), deserialized.getContent().getNumber());
}
public void testInterfaceSerialization2(){
SampleImplementation2 first = new SampleImplementation2(5, "test");
SampleInterfaceContainer toSerialize = new SampleInterfaceContainer("container", first);
first.Container = toSerialize;
GsonBuilder gsonBuilder = new GsonBuilder();
new GraphAdapterBuilder()
.addType(SampleInterfaceContainer.class)
.addType(SampleImplementation1.class)
.addType(SampleImplementation2.class)
.registerOn(gsonBuilder);
gsonBuilder.registerTypeAdapterFactory(new InterfaceAdapterFactory(
SampleInterface.class, new Class<?>[] { SampleImplementation1.class, SampleImplementation2.class }
));
Gson gson = gsonBuilder.create();
String json = gson.toJson(toSerialize);
System.out.println(json);
SampleInterfaceContainer deserialized = gson.fromJson(json, SampleInterfaceContainer.class);
assertNotNull(deserialized);
assertEquals(toSerialize.getName(), deserialized.getName());
assertEquals(5, deserialized.getContent().getNumber());
assertEquals("test", ((SampleImplementation2)deserialized.getContent()).getName());
assertSame(deserialized, ((SampleImplementation2)deserialized.getContent()).Container);
}
}
and some sample classes (to verify that the tests pass)
public class SampleInterfaceContainer {
private SampleInterface content;
private String name;
public SampleInterfaceContainer(String name, SampleInterface content)
{
this.name = name;
this.content = content;
}
public String getName()
{
return this.name;
}
public SampleInterface getContent()
{
return this.content;
}
}
public interface SampleInterface {
int getNumber();
}
public class SampleImplementation1 implements SampleInterface{
private int number;
public SampleImplementation1()
{
this.number = 0;
}
public SampleImplementation1(int number)
{
this.number = number;
}
public int getNumber()
{
return this.number;
}
}
public class SampleImplementation2 implements SampleInterface{
private int number;
private String name;
public SampleInterfaceContainer Container;
public SampleImplementation2()
{
this.number = 0;
this.name = "";
}
public SampleImplementation2(int number, String name)
{
this.number = number;
this.name = name;
}
public int getNumber()
{
return this.number;
}
public String getName()
{
return this.name;
}
}
While this has been a quick&dirty hack, it works like a charme.
You just have to pay attention at the order of the operations during the initialization of GsonBuilder. You have to initialize and register the GraphAdapterBuilder first and only after register this factory.
It has been funny (if a bit tricky since I'm not a Java expert).