I have following json:
{
"list": [
{
"BaseClass": {
"id": 0
}
},
{
"ExtendedClass1": {
"id": 1,
"sum": 100
}
},
{
"ExtendedClass1_1": {
"id": 2,
"sum": 200,
"expr": "text"
}
},
{
"ExtendedClass2": {
"id": 3,
"total": 300
}
}
]
}
Also I have following classes with declared inheritance:
MetaClass.java
import java.util.ArrayList;
import com.google.gson.Gson;
public class MetaClass{
public ArrayList<BaseClass> list = new ArrayList<BaseClass>();
public static void main(String[] args) {
String json = "{\"list\":[{\"BaseClass\":{\"id\":0}},{\"ExtendedClass1\":{\"id\":1,\"sum\":100}},{\"ExtendedClass1_1\":{\"id\":2,\"sum\":200,\"expr\":\"text\"}},{\"ExtendedClass2\":{\"id\":3,\"total\":300}}]}";
MetaClass gson = new Gson().fromJson(json, MetaClass.class);
}
}
BaseClass.java
public class BaseClass{
public int id;
public BaseClass() {
}
}
ExtendedClass1.java
public class ExtendedClass1 extends BaseClass{
public ExtendedClass1() {
}
public int sum;
}
ExtendedClass2.java
public class ExtendedClass2 extends BaseClass {
public ExtendedClass2() {
}
public int total;
}
ExtendedClass1_1.java
public class ExtendedClass1_1 extends ExtendedClass1 {
public ExtendedClass1_1() {
}
public String expr;
}
Also, there could be a lot of such classes with multilevel inheritance. I try to make this example simpler. How correctly parse mentioned json string? Assume please that I could not change input json, only I can change classes and write custom fromJson command somehow...
EDIT: Of course, I can add to BaseClass.java all attributes that could be met in json (see below), but it seems not elegant solution.
public ExtendedClass1 ExtendedClass1;
public ExtendedClass2 ExtendedClass2;
public ExtendedClass1_1 ExtendedClass1_1;
You can write a custom TypeAdapter and register it to gsonBuilder. In your custom type adapter's read method, you have to manage mappings to create correct instances of the classes that you defined. I used the tags of your list json array's items:
public class CustomTypeAdapter extends TypeAdapter<BaseClass> {
#Override
public BaseClass read(JsonReader in) throws IOException {
BaseClass item = null;
Gson gson = new Gson();
JsonParser jsonParser = new JsonParser();
JsonObject jo = (JsonObject)jsonParser.parse(in);
JsonElement je = null;
if ((je = jo.get("BaseClass")) != null) {
item = gson.fromJson(je, BaseClass.class);
} else if((je = jo.get("ExtendedClass1")) != null) {
item = gson.fromJson(je, ExtendedClass1.class);
} else if((je = jo.get("ExtendedClass1_1")) != null) {
item = gson.fromJson(je, ExtendedClass1_1.class);
} else if((je = jo.get("ExtendedClass2")) != null) {
item = gson.fromJson(je, ExtendedClass2.class);
}
return item;
}
#Override
public void write(JsonWriter out, BaseClass item) throws IOException {
throw new IllegalArgumentException("TypeAdapter.write method not implemented!");
}
}
Test:
String json = "{\"list\":[{\"BaseClass\":{\"id\":0}},{\"ExtendedClass1\":{\"id\":1,\"sum\":100}},{\"ExtendedClass1_1\":{\"id\":2,\"sum\":200,\"expr\":\"text\"}},{\"ExtendedClass2\":{\"id\":3,\"total\":300}}]}";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(BaseClass.class, new CustomTypeAdapter());
Gson gson = gsonBuilder.create();
java.lang.reflect.Type listType = new com.google.gson.reflect.TypeToken<List<BaseClass>>() {}.getType();
JsonArray jsonList = (JsonArray) (gson.fromJson(json, JsonObject.class).get("list"));
List<BaseClass> itemList = gson.fromJson(jsonList, listType);
Related
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'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;
}
}
}
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'm having some issues to deserialize a Json array that follows this format:
[
{
"ChildList":[
{
"ChildList":[
],
"Id":110,
"Name":"Books",
"ApplicationCount":0
}
],
"Id":110,
"Name":"Books",
"ApplicationCount":0
}
]
It's basically an array of Categories where each category can also have a List of sub-categories, and so on and so on.
My class model looks a little like this:
public class ArrayOfCategory{
protected List<Category> category;
}
public class Category{
protected ArrayOfCategory childList;
protected int id;
protected String name;
protected int applicationCount;
}
Now, Gson obviously complains about the circular reference. Is there any way to parse this Json input given that I can't assume how many levels of categories there are?
Thanks in advance.
Edit:
Just in case someone has a similar problem, based on Spaeth answer I adapted the solution to a more general case using reflection. The only requirement is that the List of objects represented by the JSON array is wrapped in another class (like Category and ArrayOfCategory in my example). With the following code applied to my original sample, you can just call "deserializeJson(jsonString,ArrayOfCategory.class)" and it will work as expected.
private <T> T deserializeJson(String stream, Class<T> clazz) throws PluginException {
try {
JsonElement je = new JsonParser().parse(stream);
if (je instanceof JsonArray) {
return deserializeJsonArray(clazz, je);
} else {
return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create().fromJson(stream, clazz);
}
} catch (Exception e) {
throw new PluginException("Failed to parse json string: " + ((stream.length() > 20) ? stream.substring(0, 20) : stream) + "... to class " + clazz.getName());
}
}
private <T> T deserializeJsonArray(Class<T> clazz, JsonElement je) throws InstantiationException, IllegalAccessException {
ParameterizedType listField = (ParameterizedType) clazz.getDeclaredFields()[0].getGenericType();
final Type listType = listField.getActualTypeArguments()[0];
T ret = clazz.newInstance();
final Field retField = ret.getClass().getDeclaredFields()[0];
retField.setAccessible(true);
retField.set(ret, getListFromJsonArray((JsonArray) je,(Class<?>) listType));
return ret;
}
private <E> List<E> getListFromJsonArray(JsonArray je, Class<E> listType) {
Type collectionType = new TypeToken<List<E>>(){}.getType();
final GsonBuilder builder = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
Gson jsonParser = builder.create();
return jsonParser.fromJson(je, collectionType);
}
Maybe you could try this:
com.google.gson.Gson gson = new GsonBuilder().create();
InputStreamReader reader = new InputStreamReader(new FileInputStream(new File("/tmp/gson.txt")));
Collection<Category> fromJson = gson.fromJson(reader, new TypeToken<Collection<Category>>() {}.getType());
System.out.println(fromJson);
you will get a good result.
The "magic" occurs here: new TypeToken<Collection<Category>>() {}.getType()
The entire code is:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.List;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
public class GsonCircularReference {
public class Category {
protected List<Category> childList;
protected int id;
protected String name;
protected int applicationCount;
public List<Category> getChildList() {
return childList;
}
public void setChildList(final List<Category> childList) {
this.childList = childList;
}
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public int getApplicationCount() {
return applicationCount;
}
public void setApplicationCount(final int applicationCount) {
this.applicationCount = applicationCount;
}
#Override
public String toString() {
return "Category [category=" + childList + ", id=" + id + ", name=" + name + ", applicationCount="
+ applicationCount + "]";
}
}
public static void main(final String[] args) throws JsonSyntaxException, JsonIOException, FileNotFoundException {
com.google.gson.Gson gson = new GsonBuilder().create();
InputStreamReader reader = new InputStreamReader(new FileInputStream(new File("/tmp/gson.txt")));
Collection<Category> fromJson = gson.fromJson(reader, new TypeToken<Collection<Category>>() {}.getType());
System.out.println(fromJson);
}
}
JSON file is:
[
{
"childList":[
{
"childList":[
],
"id":110,
"Name":"Books",
"applicationCount":0
}
],
"id":110,
"name":"Books",
"applicationCount":0
}
]
Take a look at GraphAdapterBuilder. You'll need to include it in your app, but it can serialize arbitrary graphs of objects.
I have this Json code:
{
"term" : {
"PrincipalTranslations" : {
"0" : {
termine:"casa",
traduzione:"home"
}
"1" :{
termine:"testa",
traduzione:"head"
}
"2" :{
termine:"dito",
traduzione:"finger"
}
}
}
}
How can I deserialize the object 0, 1, 2??
If instead of object 0, 1, 2 I wrote object "zero" (and stop), it works!
I've used this implementation:
public class Item {
private term term;
public term getTERM() {
return term;
}
}
public class term {
private PrincipalTranslations PrincipalTranslations;
public PrincipalTranslations getPrincipalTranslations() {
return PrincipalTranslations;
}
}
public class PrincipalTranslations {
private zero zero;
public zero getZero() {
return zero;
}
}
public class zero {
private String termine;
public String gettermine() {
return termine;
}
}
and use it so, it print (in the right way) "casa"
public class MainClass {
public static void main(String[] args) throws IOException {
FileReader reader = new FileReader("/home/peppe/test_ff");
Gson gson = new GsonBuilder().create();
Item p = gson.fromJson(reader, Item.class);
System.out.print(p.getTERM().getPrincipalTranslations().getZero().gettermine());
reader.close();
}
}
If you want to call the object zero, than in your ’Principal Translations‘ class use the ’SerializedName’ annotation: http://google-gson.googlecode.com/svn/tags/1.2.3/docs/javadocs/com/google/gson/annotations/SerializedName.html
It will look like this:
#SerializedName("0")
public Zero zero;