-->

Using Gson to elegantly handle nested json objects

2020-07-16 03:37发布

问题:

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.

回答1:

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
    ...
}


回答2:

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.