1

I have to parse a webservice with gson that has a key associated sometimes with a JsonObject (ie Elements) :

"elements": {
   "sheets": 1,
   "questions": 6
}

sometimes with an array...

"elements": [
    {
       "_id": "51824208fbca3b0398c8374a",
       "collection": "medias",
       "name": "Document Powerpoint",
       "customNameInModule": true
    },
    {....}
]

So In my model that contains this element I have to type it with JsonElement.

@SerializedName("elements")
@Expose
private JsonElement elements;

My idea was then to build my accessors this way :

private List < Element > listElement;

public List < Element > getListElements() {
    if (listElement == null) {
        Type listType = (new TypeToken < List < Element >> () {}).getType();
        listElement = (new Gson()).fromJson(elements.toString(), listType);
        return listElement;
    } else {
        return listElement;
    }
}

private ElementsObject objectElement;

public ElementsObject getObjectElement() {
    if (objectElement == null) {
        objectElement = (new Gson()).fromJson(elements.toString(), ElementsObject.class);
        return objectElement;
    } else {
        return objectElement;
    }
}

but now my model is no longer Serializable..

java.lang.RuntimeException: Parcelable encountered IOException writing serializable object

What should I do ?

Thanks,

Renaud Favier
  • 1,645
  • 1
  • 17
  • 33
  • you need to yell at whoever did this webservice until they change it for something that makes sense. – njzk2 Dec 14 '15 at 17:18
  • this : http://stackoverflow.com/questions/14582440/how-to-exclude-field-from-class-serialization-in-runtime ? (plus, when the deserialization is over, save the actual content of the json object into a field of your object) – njzk2 Dec 14 '15 at 17:19
  • It's a web service used by a complex javascript client, and It will be very costly for them to change it.. but I agree with you, unfortunatly I don't see it comming – Renaud Favier Dec 14 '15 at 17:20
  • oh for my Unserializable exception ? – Renaud Favier Dec 14 '15 at 17:22
  • What exactly is not serializable in my code I quite don't see why not – Renaud Favier Dec 14 '15 at 17:22
  • `JsonElement` is not serializable. – njzk2 Dec 14 '15 at 17:23
  • or, if you have a way in advance to know which result you'll receive, create 2 different api calls with different result, with 2 different models to back the response. What are you using to call the API? – njzk2 Dec 14 '15 at 17:23
  • I'm using retrofit, and yes In this case I know in advance what will look like my json. But I have a similar problem somewhere else where it's not the case – Renaud Favier Dec 14 '15 at 17:28
  • Can't use JsonElement then I need to serialize that field.. – Renaud Favier Dec 14 '15 at 17:31

2 Answers2

1

You have several options:

  1. Use 2 models:

One object

public static class ModelOne implements Serializable {

    @Expose
    @SerializedName("elements")
    ElementsObject element;
}

A list

public static class ModelList implements Serializable {

    @Expose
    @SerializedName("elements")
    List<ElementsObject> elements;
}

And then use one or the other. I assume you know in advance which you are facing, since your accessors depend on that. (you can't call getObjectElement if you have a list, so before calling getObjectElement, you must know that there is only one element)

  1. use transient field

which excludes the field from serialization

public static class ModelTransient implements Serializable {

    @Expose
    @SerializedName("elements")
    transient JsonElement elements;

    private List < Element > listElement;
    private ElementsObject objectElement;

    public void setListElements() {
        Type listType = (new TypeToken < List < Element >> () {}).getType();
        listElement = (new Gson()).fromJson(elements.toString(), listType);
    }

    public List < Element > getListElements() {
        return listElement;
    }


    public void setObjectElement() {
        objectElement = (new Gson()).fromJson(elements.toString(), ElementsObject.class);
    }

    public ElementsObject getObjectElement() {
        return objectElement;
    }
}

And call set*** right after the gson parsing.

  1. Use a custom factory. See Gson handle object or array
Community
  • 1
  • 1
njzk2
  • 38,969
  • 7
  • 69
  • 107
1

Here is what I finaly have done

I built my class this way :

public class CourseElement implements Serializable{

    private CourseElementDeserializationType type;

    public CourseElementDeserializationType getType() {
        return type;
    }

    private List<Element> listElement;
    private ElementsObject objectElement;


    public CourseElement(List<Element> listElement) {
        type = CourseElementDeserializationType.ARRAY;
        this.listElement = listElement;
    }

    public CourseElement(ElementsObject objectElement){
        type = CourseElementDeserializationType.OBJECT;
        this.objectElement = objectElement;
    }


    public List<Element> getListElements() {
        return listElement;
    }

    public ElementsObject getObjectElement() {
        return objectElement;
    }
}

And I built a type converter :

public class CourseElementTypeConverter implements JsonSerializer<CourseElement>, JsonDeserializer<CourseElement> {
    // No need for an InstanceCreator since DateTime provides a no-args constructor

    @Override
    public JsonElement serialize(CourseElement src, Type srcType, JsonSerializationContext context) {
        switch (src.getType()) {
            case ARRAY:
                Type listType = (new TypeToken<List<Element>>() {
                }).getType();
                return new JsonPrimitive((new Gson()).toJson(src.getListElements(), listType));
            case OBJECT:
                return new JsonPrimitive((new Gson()).toJson(src.getObjectElement(), ElementsObject.class));
            default:
                return null;
        }
    }

    @Override
    public CourseElement deserialize(JsonElement json, Type type, JsonDeserializationContext context)
            throws JsonParseException {

        CourseElementDeserializationType courseType;
        if (json.isJsonArray()) {
            courseType = CourseElementDeserializationType.ARRAY;
        } else {
            courseType = CourseElementDeserializationType.OBJECT;
        }

        switch (courseType) {
            case ARRAY:
                Type listType = (new TypeToken<List<Element>>() {
                }).getType();
                List<Element> list = (new Gson()).fromJson(json, listType);
                return new CourseElement(list);
            case OBJECT:
                return new CourseElement((new Gson()).fromJson(json, ElementsObject.class));
            default:
                return null;
        }
    }
}

and the json builder that I use with retrofit :

GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(DateTime.class, new DateTimeTypeConverter());
        gsonBuilder.registerTypeAdapter(CourseElement.class, new CourseElementTypeConverter());
        Gson gson = gsonBuilder.create();

thanks a lot to njzk2 for the help

Renaud Favier
  • 1,645
  • 1
  • 17
  • 33
  • looks ok. You could move all the Json-related stuff to your converter (e.g. have 2 constructors in your model, one with the Object, one with the List) to avoid cluttering your model. – njzk2 Dec 15 '15 at 14:40