I'm retrieving comments from the Reddit API. The model is threaded such that each Comment can internally have a List of Comments, named replies. Here's an example of how a JSON response would look:
[
{
"kind":"Listing",
"data":{
"children":[
{
"data":{
"body":"comment",
"replies":{
"kind":"Listing",
"data":{
"children":[
{
"data":{
"body":"reply to comment",
"replies":""
}
}
]
}
}
}
}
]
}
}
]
Here is how I model this with POJOs. The response above would be considered a List of CommentListings.
public class CommentListing {
#SerializedName("data")
private CommentListingData data;
}
public final class CommentListingData {
#SerializedName("children")
private List<Comment> comments;
}
public class Comment {
#SerializedName("data")
private CommentData data;
}
public class CommentData {
#SerializedName("body")
private String body;
#SerializedName("replies")
private CommentListing replies;
}
Note how the bottom level CommentData POJO refers to another CommentListing called "replies".
This model works until GSON reaches the last child CommentData where there are no replies. Rather than providing a null, the API is providing an empty String. Naturally, this causes a GSON exception where it expects an object but finds a String:
"replies":""
Expected BEGIN_OBJECT but was STRING
I attempted to create a custom deserializer on the CommentData class, but due to the recursive nature of the model it seems not to reach the bottom levels of the model. I imagine this is because I'm using a separate GSON instance to complete deserialization.
#Singleton
#Provides
Gson provideGson() {
Gson gson = new Gson();
return new GsonBuilder()
.registerTypeAdapter(CommentData.class, new JsonDeserializer<CommentData>() {
#Override
public CommentData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject commentDataJsonObj = json.getAsJsonObject();
JsonElement repliesJsonObj = commentDataJsonObj.get("replies");
if (repliesJsonObj != null && repliesJsonObj.isJsonPrimitive()) {
commentDataJsonObj.remove("replies");
}
return gson.fromJson(commentDataJsonObj, CommentData.class);
}
})
.serializeNulls()
.create();
}
How can I force GSON to return a null instead of a String so that it doesn't try to force a String into my POJO? Or if that's not possible, manually reconcile the data issue? Please let me know if you need additional context or information. Thanks.
In general your code looks good, but I would recommend a few things:
Your type adapters should not capture Gson instances from outside. Type adapter factories (TypeAdapterFactory) are designed for this purpose. Also, in JSON serializers and deserializers you can implicitly refer it through JsonSerializationContext and JsonDeserializationContext respectively (this avoids infinite recursion in some cases).
Avoid modification JSON objects in memory as much as possible: serializers and deserializers are just a sort of pipes and should not bring you surprises with modified objects.
You can implement a generic "empty string as a null" type deserializer and annotate each "bad" field that requires this kind of deserialization strategy. You might consider it's tedious, but it gives you total control wherever you need it (I don't know if Reddit API has some more quirks like this).
public final class EmptyStringAsNullTypeAdapter<T>
implements JsonDeserializer<T> {
// Let Gson instantiate it itself
private EmptyStringAsNullTypeAdapter() {
}
#Override
public T deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
if ( jsonElement.isJsonPrimitive() ) {
final JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive();
if ( jsonPrimitive.isString() && jsonPrimitive.getAsString().isEmpty() ) {
return null;
}
}
return context.deserialize(jsonElement, type);
}
}
And then just annotate the replies field:
#SerializedName("replies")
#JsonAdapter(EmptyStringAsNullTypeAdapter.class)
private CommentListing replies;
Related
I receive an invalid json (extra comma in the end of the list) in request body, which is being successfully deserialized by GSON library. On inspection I see that GSON is inserting a NULL object in the end.
{
"content": "Test 2",
"timestamp": 1494311947530,
"entities": [
{"name": "entity1"},
{"name": "entity2"},
{"name": "entity3"},
{"name": "entity4"},
{"name": "entity5"},
]
}
Is there a way by which I can either instruct GSON not to accept invalid json or remove NULL objects from JsonArray.
I have tried registering type adapter for Set.class but I can't proceed further with this solution as it is not possible to get Type of the parameterized object.
public class RemoveNullCollectionSerializer<T> implements JsonDeserializer<Set<T>> {
#Override
public Set<T> deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonArray elements = jsonElement.getAsJsonArray();
Set<T> result = new HashSet();
for (JsonElement element : elements) {
if (element.isJsonNull()) continue;
T value = (T) context.deserialize(element, Object.class);
result.add(value);
}
return result;
}
}
I'm trying not to register custom adapters as there are a lot of models in the project and each will require one adapter, which will be a big task.
I'm sorry but Gson seems not be able to do this. There's a special mode in Gson instructing it to work in "lenient" mode -- this is why you're getting a null element in the result collection. The lenient mode tells Gson ignore some soft invalid JSON issues, one of them is reading trailing array elements and object properties. If you take a look at CollectionTypeAdapterFactory that actually is responsible for collections read, you see:
#Override public Collection<E> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
Collection<E> collection = constructor.construct();
in.beginArray();
while (in.hasNext()) {
E instance = elementTypeAdapter.read(in);
collection.add(instance);
}
in.endArray();
return collection;
}
As you can see, an element instance is read and always added to the result collection. The elementTypeAdapter.read may fail in non-lenient mode, but this means that the object will not be constructed totally. You can check it like this:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !Collection.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(this, typeToken);
return new TypeAdapter<T>() {
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
delegateAdapter.write(out, value);
}
#Override
public T read(final JsonReader in)
throws IOException {
final boolean wasLenient = in.isLenient();
try {
in.setLenient(false);
return delegateAdapter.read(in);
} finally {
in.setLenient(wasLenient);
}
}
};
}
})
.create();
Note that the read method disables the lenient mode temporarily and then restores it back. The code above would cause
Use JsonReader.setLenient(true) to accept malformed JSON at line 20 column 3 path $.entities[5]
for you trailing "emptiness" JSON document. There is no way to check if the next JSON token would fail or not according to the lenient mode in Gson. Not the best per se (but probably the only way) is using reflection in order to get the JsonReader backing reader (Reader actually) and decorating it with a new JsonReader overriding hasNext() method or something like that. It would be nice if JsonReader would support a method to check if its last value was generated because of the lenient mode set to true. But even if it supported something like that, there would be no guarantee to remove the last element because a particular type adapter might return a non-modifiable collection.
By the way, I think this issue should be posted to https://github.com/google/gson/issues/ -- I'd love to get the Gson team feedback.
To start off, I have looked at a few other answers for similar questions, but they do not answer my particular situation.
I'm parsing JSON messages which consist of a body and a header, where the header stores what type of object the body is:
{
"body": {
"eventName": "someEventName"
},
"header": {
"purpose": "event"
}
}
In Java, I've modeled this structure using the following classes:
public class Message {
public Body body;
public Header header;
}
public class Header {
public String purpose; // Marks what child class the body of the message uses
}
public abstract class Body {
// Child classes store additional fields
}
// Example implementation of the body class
public class EventBody extends Body {
public String eventName; // Name of some event
}
After doing some research, I found that RuntimeTypeAdapterFactory is normally used to parse/write polymorphic objects; however, the RutimeTypeAdapterFactory class relies on the type being stored in the base class of the polymorphic object (i.e. Body). But in this scenario, that's not the case ― the type is stored in another object, Header.
What would be the best way to go about parsing these kind of objects? I'd like to avoid having to write a custom Serializer/Deserializer for compactness, but I wouldn't mind writing them if it's necessary.
I realize that asking for a solution that doesn't involve a custom Serializer/Deserializer is a bit ridiculous, as this is exactly the type of scenario they'd be used in (I was thinking I could get away with a custom TypeAdapterFactory, but using a Serializer/Deserializer is easier).
Anyway, for my scenario, a combination of a custom Serializer/Deserializer for the Message class seems to work fine. Since I already use an enum to track different message purposes and their string names, I decided to simply add an additional field to that enum to store the corresponding body class.
MessagePurpose Enum:
public enum MessagePurpose {
EVENT("event", EventBody.class);
public final String purposeName;
public final Class bodyClass;
MessagePurpose(String purposeName, Class classi) {
this.purposeName = purposeName;
bodyClass = classi;
}
}
MessageSerializer:
public class MessageSerializer implements JsonSerializer<Message> {
#Override
public JsonElement serialize(Message message, Type type, JsonSerializationContext jsc) {
if(message == null) {
return null;
}
JsonObject messageObj = new JsonObject();
// Get the class representing the body object from the purpose enum
Class bodyClassType = message.getPurpose().bodyClass;
messageObj.add("body", jsc.serialize(message.getBody(), bodyClassType));
messageObj.add("header", jsc.serialize(message.getHeader(), Header.class));
return messageObj;
}
}
MessageDeserializer:
public class MessageDeserializer implements JsonDeserializer<Message> {
#Override
public Message deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
Header header = jdc.deserialize(je.getAsJsonObject().get("header"), Header.class);
// Get the class representing the body object from the purpose enum
Class bodyClassType = header.getPurpose().bodyClass;
Body body = jdc.deserialize(je.getAsJsonObject().get("body"), bodyClassType);
return new Message(body, header);
}
}
Main function to test with:
public static void main(String[] args) {
GsonBuilder gb = new GsonBuilder();
// Register the Message class since I need to access info in the header
gb.registerTypeAdapter(Message.class, new MessageDeserializer());
gb.registerTypeAdapter(Message.class, new MessageSerializer());
Gson gson = gb.setPrettyPrinting().create();
EventBody event = new EventBody(EventType.SOME_EVENT_NAME);
String eventJson = gson.toJson(event.getAsMessage());
System.out.println(eventJson);
Message newEvent = gson.fromJson(eventJson);
System.out.println("\nEvent type: " + ((EventBody) newEvent.getBody()).getEventName());
}
The above test class prints:
{
"body": {
"eventType": "someEventName"
},
"header": {
"purpose": "event"
}
}
Event Type: someEventName
This output matches the JSON of the Messages I'm parsing, and it seems to deserialize different types of messages just fine.
{
"localeCode": "",
"map": {
"DynamicName1": [],
"DynamicName2": [
{
"date": "2016-05-15T00:00:00",
"seqId": 1,
"status": 10
},
{
"date": "2016-05-16T00:00:00",
"seqId": 83,
"status": 10
}
],
"DynamicName3": [],
"DynamicName4": []
},
"respCode": 100,
"respMsg": "success",
"status": 1
}
How to correctly map this kind of json. If you can see that, Dynamic is a dynamic name. So far I have done this :
public class MapModel {
public MapObject map;
public static class MapObject{
public java.util.Map<String, Student> queryStudent;
public static class Student{
public String date;
public String seqId;
public String status;
}
}
}
But when run the app. I'm getting NullPointerException. Can somebody help me?
You're getting the NullPointerException accessing queryStudent of your MapObject inside your MapModel since it's not correctly filled when you're trying to deserialize your Json.
So to solve your problem look at Gson documentation where you can see:
You can serialize the collection with Gson without doing anything
specific: toJson(collection) would write out the desired output.
However, deserialization with fromJson(json, Collection.class) will
not work since Gson has no way of knowing how to map the input to the
types. Gson requires that you provide a genericised version of
collection type in fromJson(). So, you have three options:
Use Gson's parser API (low-level streaming parser or the DOM parser
JsonParser) to parse the array elements and then use Gson.fromJson()
on each of the array elements.This is the preferred approach. Here is
an example that demonstrates how to do this.
Register a type adapter for Collection.class that looks at each of the
array members and maps them to appropriate objects. The disadvantage
of this approach is that it will screw up deserialization of other
collection types in Gson.
Register a type adapter for MyCollectionMemberType and use fromJson()
with Collection.
Since your MapObject containts a java.util.Map but your class itself it's not generic, I think that a good approach for your case is create a Deserializer.
Before this try to clean up your class definition, to provide constructors to make the deserializer easy to build. Your POJO classes could be:
Student class
public class Student{
public String date;
public String seqId;
public String status;
public Student(String date, String seqId, String status){
this.date = date;
this.seqId = seqId;
this.status = status;
}
}
MapObject class
Note: I change you Map definition, since in your Json seems that could be multiple students for each DynamicName (look at DynamicName2 from your question), so I use Map<String,List<Student>> instead of Map<String,Student>:
public class MapObject{
public Map<String,List<Student>> queryStudent;
public MapObject(Map<String,List<Student>> value){
this.queryStudent = value;
}
}
MapModel class
public class MapModel {
public MapObject map;
}
Now create a Deserializer for your MapObject:
public class MapObjectDeserializer implements JsonDeserializer<MapObject> {
public MapObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
Map<String,List<Student>> queryStudents = new HashMap<String,List<Student>>();
// for each DynamicElement...
for (Map.Entry<String,JsonElement> entry : json.getAsJsonObject().entrySet()) {
List<Student> students = new ArrayList<Student>();
// each dynamicElement has an Array so convert and add an student
// for each array entry
for(JsonElement elem : entry.getValue().getAsJsonArray()){
students.add(new Gson().fromJson(elem,Student.class));
}
// put the dinamic name and student on the map
queryStudents.put(entry.getKey(),students);
}
// finally create the mapObject
return new MapObject(queryStudents);
}
}
Finally register the Deserializer and parse your Json:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MapObject.class, new MapObjectDeserializer());
Gson gson = builder.create();
MapModel object = gson.fromJson(YourJson,MapModel.class);
DISCLAIMER: For fast prototyping I test this using groovy, I try to keep the Java syntax but I can forget something, anyway I think that this can put you on the right direction.
Hope it helps,
I'm using Gson to parse responses from a server on Android. Each response has some useless (to me) data on it that complicates my Gson models. Here is the general hierarchy of json returned:
response: {
date: 1406253006807,
otherUselessData1: "This is some useless data",
otherUselessData2: "This is some useless data",
usefulJsonObject: { <---- This is really the object that I care about
}
}
Everything above or at the same level as usefulJsonObject I could really do without. The useless data is returned for every request, and the actual response is embedded beneath as the usefulJsonObject. This wouldn't be a big problem but it's really cluttering up my gson model objects.
For example:
Let's say I have 3 requests I can make: A, B, and C. For each response it seems I need to make a minimum of 3 custom classes.
public class ResponseA {
#SerializedName("response") ResponseObjectA responseObject;
public static class ResponseObjectA {
#SerializedName("usefulJsonObject") UsefulObjectA usefulObject;
}
public static class UsefulObjectA {
}
}
I've tried a few solutions, but I haven't found anything elegant that wouldn't add an extra step to my process. I'm using retrofit to do my http requests and it's really nice that it just returns the fully parsed gson object to me. I've thought of other solutions like having the useful object just be a JsonElement and then doing a 2nd gson call after the first comes back. Again, not ideal.
I just wanted to know if I was missing something. Surely I'm not the only one who's encountered something like this, and so I thought I'd ask how other people would handle something like this.
It is initialization Instance value, not NULL value. Check my example.
Address.java
public class Address {
public Address(){
}
}
Person.java
public class Person {
private String name;
private String nrc;
private Address address;
public Person(String name, String nrc, Address address) {
this.name = name;
this.nrc = nrc;
this.address = address;
}
}
The following Json string is equalvent to
Person person = new Person("Zaw Than Oo", "11111", null);
{
"name": "Zaw Than Oo",
"nrc": "11111"
}
The following Json string is equalvent to
Person person = new Person("Zaw Than Oo", "11111", new Address());
{
"name": "Zaw Than Oo",
"nrc": "11111",
"address": {} <-- here use less object for you.
}
Even if you don't create new Instance, Other lib/api(you used) may be create that instance by Reflection.
Short to the Point
{
...
"xxx": {} --> new instance without data/value
...
}
{
...
--> null value
...
}
I never found an elegant way dealing with just Gson. I tried several options with Generics, all of which didn't work or left something to be desired.
Since I'm using Retrofit, I decided to override the GsonConverter, and just filter out the unnecessary information from all my requests. It ends up not being as flexible, as in I can't use the same Retrofit network interface for calls to other servers, but I'm not really doing that, and it also has the down side of having 2 rounds of json parsing calls (meh). You could probably do this more efficiently, but this is working for me for now.
public class CustomGsonConverter extends GsonConverter {
private Gson mGson;
public CustomGsonConverter(Gson gson) {
super(gson);
this.mGson = gson;
}
public CustomGsonConverter(Gson gson, String encoding) {
super(gson, encoding);
this.mGson = gson;
}
#Override public Object fromBody(TypedInput body, Type type) throws ConversionException {
try {
CustomResponse customResponse = mGson.fromJson(new InputStreamReader(body.in()), CustomResponse.class);
return mGson.fromJson(customResponse.responseObject.data, type);
} catch (IOException e) {
throw new ConversionException(e);
}
}
public static class CustomResponse {
#SerializedName("rsp") ResponseObject responseObject;
public static class ResponseObject {
// #SerializedName("date") long date;
#SerializedName("data") JsonElement data;
}
}
}
Maybe there is a better way that I'm just not realizing.
I am using google gson-2.2.1 library for parsing large response of JSON.
I have to parse a JSON response where structure may vary.
First case, when the response contains more than one team:
"Details":{
"Role":"abc",
"Team":[
{
"active":"yes",
"primary":"yes",
"content":"abc"
},
{
"active":"yes",
"primary":"yes",
"content":"xyz"
}
],
Second case, when only one team is passed:
"Details":{
"Role":"abc",
"Team":
{
"active":"yes",
"primary":"yes",
"content":"abc"
}
}
There are my base classes used for parsing:
class Details {
public String Role;
public ArrayList<PlayerTeams> Team = new ArrayList<PlayerTeams>();
PlayerTeams Team; // when JsonObject
}
class PlayerTeams {
public String active;
public String primary;
public String content;
}
The problem is that I can not use ArrayList<PlayerTeams> when I have only one of them and it's returned as JsonObject.
Gson can identify static format of JSON response. I can trace full response dynamically by checking if "Team" key is instance of JsonArray or JsonObject but it would be great if a better solution is available for that.
Edit :
If my response is more dynamic..
"Details":{
"Role":"abc",
"Team":
{
"active":"yes",
"primary":"yes",
"content":"abc"
"Test":
{
"key1":"value1",
"key2":"value2",
"key3":"value3"
}
}
}
In my edited question, I am facing problem while my response is more dynamic..Team and Test can be JsonArray or JsonObject.. It really harassing me because sometime Test object may array when more data, may object when single data, string when no data. There is no consistency in response.
You need a type adapter. This adapter would be able to distinguish which format is coming and instance the right object with the right values.
You can do this by:
implement your own type adapter by creating a class that implements JsonSerializer<List<Team>>, JsonDeserializer<List<Team>>, of course JsonSerializer is just needed in case you need to serialize it in that matter too.
Register the type adapter to you GsonBuilder like: new GsonBuilder().registerTypeAdapter(new TypeToken<List<Team>>() {}.getType(), new CoupleAdapter()).create()
The deserialize method could look like:
public List<Team> deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws com.google.gson.JsonParseException {
if (json.isJsonObject()) {
return Collections.singleton(context.deserialize(json, Team.class));
} else {
return context.deserialize(json, typeOfT);
}
}