简体   繁体   中英

GSON: JSON deserialization to variable type (List/String)

At this point it's already an old question and I've probably read every related topic on SO.

But to the point. I need some advice or correction maybe?

For some reason we have generatable Jsons of 2 types:

{"data": {"id": "value"}} and {"data":[{"id": "value"}]}

Object and Array. There are also other params but they doesn't matter here. "id" is differ for every request. Sometimes it's userId, portfolioId etc. So I get "id" and pass it to related var.

For a long time I was working with the first case. And created POJO like this:

Data.class

public class Data {

@SerializedName("id")
@Expose
private String id;

public Data() {
}

public Data(String id) {
    super();
    this.id = id;
}

protected String getId() {
    return id;
}

And I adress "data" paramets via User.class .

@JsonAdapter(UserDeserializer.class)
public Data data;


public Data getData() {
    return data;
}

public void setData(Data data) {
    this.data = data;
}


public User() {
}

public User(Data data) {
    super();
    this.data = data;
}

Gson gson = new Gson();

public String getPortfolioList(String tokenId, String userId) {
    Call<User> call = apiRequest.getPortfolioList(userId, tokenId);

    try {
        User newResult = gson.fromJson(String.valueOf(call.execute().body()), User.class);
        System.out.println(newResult.getData().getId());
    } catch (IOException e) {
        e.printStackTrace();
    }
    return getPortfolioId();
}

Deserializer.class

 public class UserDeserializer implements JsonDeserializer<User> {

    private Type listType = new TypeToken<List<Data>>(){}.getType();

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

        User user = new User();
        JsonElement jsonElement;
        if (json.isJsonArray()) {
            jsonElement = json.getAsJsonArray();
            user.data = context.deserialize(jsonElement,listType);
//            user.data = new Gson().fromJson(jsonElement, new TypeToken<List<Data>>() {}.getType());
        } else {
            jsonElement = json.getAsJsonObject();
            user.data = context.deserialize(jsonElement, Data.class);
//            user.setData(new Gson().fromJson(jsonElement, new TypeToken<Data>() {}.getType()));
        }
        return user;
    }
}

Gson builder in BaseApi class just in case:

Gson gson = new GsonBuilder().registerTypeAdapter(UserDeserializer.class, new UserDeserializer()).setLenient().create();

Without custom deserialization and Array JSON issue this would work perfectly. But now I have to determine "data" 's exact type I get.

In above case I get java.lang.ClassCastException: java.util.ArrayList cannot be cast to auto.Rest.Data

I assume I have to create another Data class (for example there will be DataObject & DataArray ) and describe every parameter as I did before in Data.class to get this work? I think I do something wrong during deserialization but I'm not sure where tbh.

Or am I wrong and it is possible to invoke Data as List and Data as an Object of the same class?

I'm working on this for several days already(?) and was thinking about use generics instead of Gson help, yeah, I'm desperate. So any help appreciated.

if there is always one object, just add

json.getAsJsonArray().get(0);

public class UserDeserializer implements JsonDeserializer<User> {

  private Type listType = new TypeToken<List<Data>>(){}.getType();

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

    User user = new User();
    JsonElement jsonElement;
    if (json.isJsonArray()) {
      jsonElement = json.getAsJsonArray().get(0);
      user.data = context.deserialize(jsonElement,listType);
//            user.data = new Gson().fromJson(jsonElement, new TypeToken<List<Data>>() {}.getType());
    } else {
      jsonElement = json.getAsJsonObject();
      user.data = context.deserialize(jsonElement, Data.class);
//            user.setData(new Gson().fromJson(jsonElement, new TypeToken<Data>() {}.getType()));
    }
    return user;
  }

}

if there are more objects, change field data to the list

public class UserDeserializer implements JsonDeserializer<User> {

  private Type listType = new TypeToken<List<Data>>(){}.getType();

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

    User user = new User();
    JsonElement jsonElement;
    if (json.isJsonArray()) {
      jsonElement = json.getAsJsonArray();
      user.data = context.deserialize(jsonElement,listType);
//            user.data = new Gson().fromJson(jsonElement, new TypeToken<List<Data>>() {}.getType());
    } else {
      jsonElement = json.getAsJsonObject();
      List<Data> data = new ArrayList<Data>();
      data.add(context.deserialize(jsonElement, Data.class)) ;
      user.data = data ;
//            user.setData(new Gson().fromJson(jsonElement, new TypeToken<Data>() {}.getType()));
    }
    return user;
  }

}

and change User.class field data to List

public List<Data> data;

this is a similar topic in kotlin language link

If you always have object or one-element array you can write custom deserialiser as below:

class OneOrElementJsonDeserializer<T> implements JsonDeserializer<T> {

    @Override
    public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        if (json instanceof JsonArray) {
            final JsonArray array = (JsonArray) json;
            final int size = array.size();
            if (size == 0) {
                return null;
            }

            return context.deserialize(array.get(0), typeOfT);
        }

        return context.deserialize(json, typeOfT);
    }
}

Your example model after simplification looks like below:

class User {

    @JsonAdapter(OneOrElementJsonDeserializer.class)
    private Data data;

    public User() {
    }

    public User(Data data) {
        super();
        this.data = data;
    }

    public Data getData() {
        return data;
    }

    public void setData(Data data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "User{" +
                "data=" + data +
                '}';
    }
}

class Data {

    private String id;

    protected String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Data{" +
                "id='" + id + '\'' +
                '}';
    }
}

Example usage:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.JsonAdapter;

import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Type;

public class GsonApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        Gson gson = new GsonBuilder()
                .setPrettyPrinting()
                .create();

        User root = gson.fromJson(new FileReader(jsonFile), User.class);
        System.out.println(root);
    }
}

Above code for below JSON payload:

{
  "data": [
    {
      "id": "c87ca3fe85781007869b83f"
    }
  ]
}

prints:

User{data=Data{id='c87ca3fe85781007869b83f'}}

And for object case JSON payload:

{
  "data": {
    "id": "c87ca3fe85781007869b83f"
  }
}

prints:

User{data=Data{id='c87ca3fe85781007869b83f'}}

In case your property could contain JSON object or multi-element array see my answer to this question Mapping Json Array to Java Models . There is implemented deserialiser which handle cases like this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM