I have the following classes:
public class Top {
private String key;
...
}
public class A extends Top {
private String aValue;
...
}
public class Complex {
private String field;
private List<Top> objects;
}
I want to deserialize a json String into a "Complex" class and specify that "objects" elements are of type "A".
I have tried 2 methods:
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(A.class, new InstanceCreator<A>() {
#Override
public A createInstance(Type arg0) {
return new A();
}
}) //method 1
.registerTypeHierarchyAdapter(A.class, new My_A_Adapter()) //method 2
.create();
Complex complexObject = gson.fromJson(json, Complex.class);
A = (A) complexObject.getObjects().get(0); // This throws ClassCastException
But the type of complexObject.getObjects().get(0) is "Top" so i cannot cast it to "A".
I do not want to parameterize the Complex class, (for ex. Complex) because i want to add more collections of generic objects in time...
What solution do I have ?
Related
I have classes like
public class ClassA{
private String id;
private List<ClassB> bClass;
}
public class ClassB{
private int id;
}
public class ClassC{
private String id;
private List<ClassD> dClass;
}
public class ClassD{
private String name;
}
I am reading a json from a source which I can deserialize into a ClassA or ClassC object. I am planning to write a custom deserializer for this task like this:
public class StringToObjectDeserializer extends StdDeserializer<List<B>> {
public StringToObjectDeserializer() {
this(null);
}
public StringToObjectDeserializer(Class<?> vc) {
super(vc);
}
#Override
public List<B> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode jsonObject = p.getCodec().readTree(p);
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonObject.textValue(), new TypeReference<List<B>>() {
});
}
}
As you can see that I am typing the return object of the deserializer as List<B>. What I would like is to have a single deserializer which would return List<B> or List<D> depending on the object property that is getting deserialized into. This is because both the deserializers have the same logic, only difference being the typing. I know that type erasure happens at runtime but is there any way around this?
I have tried public class StringToObjectDeserializer extends StdDeserializer<List> as the signature, but it only returns a hash map which fails to map to the list of objects.
I want to use the deserializer by registering a SimpleModule like:
SimpleModule deSerializerModule = new SimpleModule();
deSerializerModule.addDeserializer(<what to put here?>, new StringToObjectDeserializer());
ObjectMapper mapper = new ObjectMapper()
.registerModule(deSerializerModule);
List<A> listA = mapper.readValue(mapper.writeValueAsString(readJson), new TypeReference<List<A>>() {});
Any help is appreciated!
EDIT:
The reason I require a custom deserializer is due to the need of parsing 2 different types of json inputs:
Type 1:
{
"id":"abc123",
"bClass":[{
"id":123
},{
"id":456
}]
}
Type 2:
{
"id":"abc123",
"bClass":"[{\"id\":123},{\"id\":456}]"
}
Type 1 can be parsed easily by Jackson itself and I am indeed doing so in a place but handling Type 2 is the challenge as it is a string representation of a json array.
Currently I have a hack in place which manually converts the input from Type 2 to Type 1 before putting it through the mapper but I don't find it elegant.
Json string:
[
//Object 1
{
TypeName:"CheckSpecificDday",
SpecificDay:"20160413",
Lunar:1
},
{
TypeName:"CheckSpecificDday",
SpecificDay:"20160414",
Lunar:1
},
//Object 2
{
TypeName:"CheckEveryDayDday",
StartDate:"20160413",
EndDate:"20260417",
Interval:1,
StartOption:"D",
HolidayCondition:1
},
//Object 3
{
TypeName:"CheckEveryDdayOfWeek",
StartDate:"20160413",
EndDate:"",
Interval:1,
SpecificDayOfWeek:"3",
HolidayCondition:1
},
//Object 4
{
TypeName:"CheckEveryMonthSpecificDday",
StartDate:"20160413",
EndDate:"",
Interval:1,
SpecificDD:"13,14",
HolidayCondition:1
},
//Object 5
{
TypeName:"CheckEveryYearWeek",
StartDate:"20160413",
EndDate:"",
Interval:1,
SpecificMMnthWeek:"0433",
HolidayCondition:1
}
]
I have a Json array like the above. What I want is to parse it to different object types with Gson (as I commented to make it clearer), but I dont know how to do that. Please help me. Thank you in advance!
I think there are lots of simmilar questions on SO. One, Two
One way to parse this is to use simple
Object[] result = new Gson().fromJson(json, Object[].class);
But this will give you objects of LinkedTreeMap<Integer, LinkedTreeMap<String, String>> or something like this. You can use it, but its kinda hard and you will also have problems with your integers comming as doubles.
The other approach is to create custom interface or abstract class with TypeName field if you need it:
private interface CheckInterface{}
and implement it with every POJO classes of object types you have:
private static class CheckEveryDayBase implements CheckInterface{
private String StartDate;
private String EndDate;
private int Interval;
private int HolidayCondition;
}
private static class CheckSpecificDday implements CheckInterface{
private String SpecificDay;
private int Lunar;
}
private static class CheckEveryDayDday extends CheckEveryDayBase{
private String StartOption;
}
private static class CheckEveryDdayOfWeek extends CheckEveryDayBase{
private String SpecificDayOfWeek;
}
private static class CheckEveryMonthSpecificDday extends CheckEveryDayBase{
private String SpecificDD;
}
private static class CheckEveryYearWeek extends CheckEveryDayBase{
private String SpecificMMnthWeek;
}
Then create custom desrializer for your CheckInterface:
public static class CheckInterfaceDeserializer implements JsonDeserializer<CheckInterface>{
#Override
public CheckInterface deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
JsonObject jObject = (JsonObject) json;
JsonElement typeObj = jObject.get("TypeName");
if(typeObj!= null ){
String typeVal = typeObj.getAsString();
switch (typeVal){
case "CheckSpecificDday":
return context.deserialize(json, CheckSpecificDday.class);
case "CheckEveryDayDday":
return context.deserialize(json, CheckEveryDayDday.class);
case "CheckEveryDdayOfWeek":
return context.deserialize(json, CheckEveryDdayOfWeek.class);
case "CheckEveryMonthSpecificDday":
return context.deserialize(json, CheckEveryMonthSpecificDday.class);
case "CheckEveryYearWeek":
return context.deserialize(json, CheckEveryYearWeek.class);
}
}
return null;
}
}
Here is how you can use this:
GsonBuilder builder = new GsonBuilder();
// Register custom deserializer for CheckInterface.class
builder.registerTypeAdapter(CheckInterface.class, new CheckInterfaceDeserializer());
Gson gson = builder.create();
CheckInterface[] result2 = gson.fromJson(json, CheckInterface[].class);
Let's say we have structure like this:
JSON:
{
"body": {
"cats": [
{
"cat": {
"id": 1,
"title": "cat1"
}
},
{
"cat": {
"id": 2,
"title": "cat2"
}
}
]
}
}
And corresponding POJO:
Response.class
private final Body body;
Body.class
private final Collection<CatWrapper> cats
CatWrapper.class
private final Cat cat
Cat.class
private final int id;
private final String title;
But let say now we have the same structure, but instead of Cat we receive truck
{
"body": {
"trucks": [
{
"truck": {
"id": 1,
"engine": "big",
"wheels": 12
}
},
{
"truck": {
"id": 2,
"engine": "super big",
"wheels": 128
}
}
]
}
}
I'm using GSON (default Retrofit json parser) on Android, consider this while giving solutions.
We could use generics here so response would look like:
private final Body<ListResponse<ItemWrapper<T>> but we can't since the field names are specific to a class.
QUESTION:
What Can I do to serialize it automaticaly without creating so many boilerplate classes? I don't really need separate classes like BodyCat, BodyTruck, BodyAnyOtherClassInThisStructure and I'm looking for a way to avoid having them.
#EDIT:
I've change classes (cat, dog -> cat,truck) due to inheritance confusion, classes used here as example DO NOT extends one another
One idea would be to define a custom generic deserializer. Its generic type will represent the concrete class of the list's elements wrapped in a Body instance.
Assuming the following classes:
class Body<T> {
private List<T> list;
public Body(List<T> list) {
this.list = list;
}
}
class Cat {
private int id;
private String title;
...
}
class Truck {
private int id;
private String engine;
private int wheels;
...
}
The deserializer assumes that the structure of the json is always the same, in the sense that you have an object that contains an object named "body". Also it assumes that the value in the first key-value pair of this body is a list.
Now for each element in the json array, we need again to fetch the inner object associated with each key. We deserialize this value and put it in the list.
class CustomJsonDeserializer<T> implements JsonDeserializer<Body<T>> {
private final Class<T> clazz;
public CustomJsonDeserializer(Class<T> clazz) {
this.clazz = clazz;
}
#Override
public Body<T> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject body = json.getAsJsonObject().getAsJsonObject("body");
JsonArray arr = body.entrySet().iterator().next().getValue().getAsJsonArray();
List<T> list = new ArrayList<>();
for(JsonElement element : arr) {
JsonElement innerElement = element.getAsJsonObject().entrySet().iterator().next().getValue();
list.add(context.deserialize(innerElement, clazz));
}
return new Body<>(list);
}
}
For the final step, you just need to create the corresponding type, instantiate and register the adapter in the parser. For instance for trucks:
Type truckType = new TypeToken<Body<Truck>>(){}.getType();
Gson gson = new GsonBuilder()
.registerTypeAdapter(truckType, new CustomJsonDeserializer<>(Truck.class))
.create();
Body<Truck> body = gson.fromJson(new FileReader("file.json"), truckType);
You can even return the list directly from the adapter if you want to get rid of the Body class.
With the trucks you'll get [1_big_12, 2_super big_128] as output and [1_cat1, 2_cat2] with the cats.
I would be using this approach:
public class Body {
private List<Animal> animals;
}
}
public class Animal {}
public class Dog extends Animal {}
public class Cat extends Animal {}
In this case you'll have serialization w/o any boilerplates, except fact that you have to use Gson TypeAdapter for Animal class, like:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Animal.class, new AnimalSerializer())
.create();
Where TypeAdapter should look smth like:
public class AnimalSerializer implements JsonSerializer<Animal>, JsonDeserializer<Animal> {
private static final String CLASS_META_KEY="clz";
#Override
public JsonElement serialize(Animal src, Type typeOfSrc,
JsonSerializationContext context) {
JsonElement element=null;
if (src == null) {
return element;
}
if(src instanceof Cat)
element = context.serialize(src, Cat.class);
else if(src instanceof Dog)
element = context.serialize(src, Dog.class);
else
throw new IllegalArgumentException("Unspecifiad class serializer for "+src.getClass().getName());
element.getAsJsonObject().addProperty(CLASS_META_KEY, src.getClass().getCanonicalName());
return element;
}
#Override
public Field deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Class<?> clz;
Animal animal;
JsonObject object = jsonElement.getAsJsonObject();
if (object.has(CLASS_META_KEY)) {
String className = object.get(CLASS_META_KEY).getAsString();
try {
clz = Class.forName(className);
} catch (Exception e) {
Log.e(TAG, "Can't deserialize class="+className,e);
clz = Animal.class;
}
animal = context.deserialize(jsonElement, clz);
} else {
animal = context.deserialize(jsonElement, typeOfT);
}
return animal;
}
}
public class body {
private List<cats> cats;
private List<dogs> dogs;
public class cats {
private list<cat> cat;
}
public class dogs {
private list<dog> dog;
}
}
This doesn't really reduce boilerplate code, but it should prevent you from having a separate body class with lists of classes that are just lists of your actual animals. It should make for you to just have your body class, and then a class for each animal with it's stats.
I'm new in Android programming's world and I've some problems using JSON to serialize and deserialize an ArrayList of custom objects. I know how to serialize an object, but not if it has an ArrayList as a field.
I found the GSON library but I don't really know how to use it.
I have an ArrayList of a class A which has an ArrayList of a class B. I'm serializing class A using a toJSON() method:
public class A
{
//Some fields like title, id...
private String mTitle;
private ArrayList<B> mArray; //I don't know how to serialize/deserialize this field
public A(){...}
public A(JSONObject json){...}
public JSONObject toJSON() throws JSONException //method I use to convert my object into a JSONObject
{
JSONObject json = new JSONObject();
json.put("TITLE",mTitle);
return json;
}
}
public class B
{
//some fields like a double, String...
public B(){...}
}
In case of collection, you must specify the type.
See a Guide User
In your case, I think this will solve:
Type collectionType = new TypeToken<Collection<A>>(){}.getType();
Collection<A> list = gson.fromJson(json, collectionType);
Where A is your class.
Are you trying to use Parcelable? Try something like this (pseudo code):
public class ParcelableObject implements Parcelable{
List<ObjectB> _customObjectList = new ArrayList<ObjectB>();
#Override
public void writeToParcel(Parcel outParcel_, int arg1) {
outParcel_.writeTypedList(_customObjectList); //where _customObjectList is your list variable
}
//Then in your constructor which takes in a Parcel:
public ParcelableObject(Parcel parcelIn_) {
parcelIn_.readTypedList(_customObjectList, ObjectB.CREATOR);
}
}
ObjectB
public class ObjectB implements Parcelable{
int _randomInt;
public static final Parcelable.Creator<ObjectB> CREATOR = new Parcelable.Creator<ObjectB>() {
#Override
public ObjectBcreateFromParcel(Parcel in) {
return new ObjectB(in);
}
#Override
public ObjectB[] newArray(int size) {
return new ObjectB[size];
}
};
public ObjectB(Parcel parcelIn_) {
_randomInt = parcelIn_.readInt();
}
#Override
public void writeToParcel(Parcel out_, int flags) {
out_.writeInt(_randomInt);
}
}
What is better is to do something like that:
List<YourObject> myList = new Genson().deserialize(dta.toString(), List.class);
I have some Objects
public class MyObject {
private String name;
private String city;
public MyObject(String n, String c) {
name=n; city=c;}
public String getName() {return name;}
public String getCity() {return city;}
public void setName(String n) {name=n;}
public void setCity(String c) {city =c;}
}
And i have a custom serializer:
public class MySerializer implements JsonSerializer<MyObject> {
public JsonElement serialize(final MyObject myobj, final Type type, JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("id", new JsonPrimitive(myobj.getName()));
return result;
}
}
Basically i just want to serilize only 1 of the 2 fields. this works great when do something like:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyObject.class, new MySerialize());
Gson gson = builder.create();
System.out.println(gson.toJson(new MyObject("test","something"));
however it gets a bit more complicated (and here is my question) when i have another object which is made up of "MyObject"s. How can i get the correct serializer to only serialize the one field of MyObject.
so i have another class:
public class SomeObject {
private String id;
private MyObject foo;
private MyObject bar;
...
}
and i have a custom serializer:
public JsonElement serialize(final SomeObject something, final Type type, JsonSerializationContext context) {
JsonObject result = new JsonObject();
Gson gson = new Gson();
result.add("id", new JsonPrimitive(something.getId()));
//here i need help
result.add("myobject1", new JsonPrimitive(gson.toJson(something.getFoo())));
return result;
}
I'm not sure if its best practice to create the GsonBuilder for "MyObject" inside the custom serializer for SomeObject is it?
ive tried something like:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyObject.class, new MySerialize());
builder.registerTypeAdapter(SomeObject.class, new SomeObjectSerializer());
Gson gson = builder.create();
System.out.println(gson.toJson(new SomeObject("id",new MyObject("test","something"),new MyObject("test2,"barrrrr"));
and i would exepct "{"id":"id","foo"{"id":"test"},"bar":{"id:"test2"}}
but that is not the case. bascially i want just the first field in a custom object whcih i have a seralizer for, but do i need to build that serializer inside another objects custom serializer? seems wrong, dunno why.
Note how you have access to the JsonSerializationContext in your custom JsonSerializer classes. You can call JsonSerializationContext#serialize(Object) and Gson will use a registered or default TypeAdapter to serialize that object and return a JsonElement which you can add to the outer JsonElement.