【问题标题】:GSON deserialization with generic types and generic field names具有泛型类型和泛型字段名称的 GSON 反序列化
【发布时间】:2016-01-07 16:31:21
【问题描述】:

假设我们有这样的结构:

JSON:

{
  "body": {
    "cats": [
      {
        "cat": {
          "id": 1,
          "title": "cat1"
        }
      },
      {
        "cat": {
          "id": 2,
          "title": "cat2"
        }
      }
    ]
  }
}

以及对应的POJO:

Response.class

private final Body body;

Body.class

private final Collection<CatWrapper> cats

CatWrapper.class

private final Cat cat

猫类

private final int id;
private final String title;

但是假设现在我们有相同的结构,但我们收到的是truck而不是truck

{
  "body": {
    "trucks": [
      {
        "truck": {
          "id": 1,
          "engine": "big",
          "wheels": 12
        }
      },
      {
        "truck": {
          "id": 2,
          "engine": "super big",
          "wheels": 128
        }
      }
    ]
  }
}

我在 Android 上使用 GSON(默认 Retrofit json 解析器),请在给出解决方案时考虑这一点。

我们可以在这里使用泛型,所以响应看起来像:

private final Body<ListResponse<ItemWrapper<T>> 但我们不能,因为字段名称是特定于一个类的。

问题:

如何在不创建这么多样板类的情况下自动序列化它?我真的不需要像BodyCatBodyTruckBodyAnyOtherClassInThisStructure 这样的单独类,我正在寻找一种避免使用它们的方法。

@编辑:

由于继承混淆,我更改了类(猫、狗 -> 猫、卡车),此处用作示例的类不会相互扩展

【问题讨论】:

    标签: java android json generics gson


    【解决方案1】:

    一个想法是定义一个自定义的通用反序列化器。它的泛型类型将表示包装在Body 实例中的列表元素的具体类。

    假设以下类:

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

    反序列化器假定 json 的结构总是相同的,因为您有一个包含名为“body”的对象的对象。它还假设这个body的第一个键值对中的值是一个列表。

    现在对于 json 数组中的每个元素,我们需要再次获取与每个键关联的内部对象。我们反序列化这个值并将其放入列表中。

    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);
        }
    }
    

    最后一步,只需要在解析器中创建对应的类型,实例化和注册适配器即可。例如卡车:

    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);
    

    如果您想摆脱 Body 类,您甚至可以直接从适配器返回列表。

    对于卡车,您将获得 [1_big_12, 2_super big_128] 作为输出,对于猫,您将获得 [1_cat1, 2_cat2]

    【讨论】:

      【解决方案2】:

      我会使用这种方法:

      public class Body {
          private List<Animal> animals;
         }
      }
      
      public class Animal {}
      
      public class Dog extends Animal {}
      
      public class Cat extends Animal {}
      

      在这种情况下,您将进行不带任何样板的序列化,除非您必须将 Gson TypeAdapter 用于 Animal 类,例如:

      Gson gson = new GsonBuilder()
                          .registerTypeAdapter(Animal.class, new AnimalSerializer())
                          .create();
      

      TypeAdapter 应该看起来像这样:

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

      【讨论】:

      • 看我的编辑,这些类不是同一类型,抱歉选择了混淆的名称
      • 动物也可以是界面
      • 有两个独立的未连接的东西,每个api响应都是这种格式,可以有User、Cat、Plane、X509Certificate等类
      • 不过,总体而言,这种方法似乎是合理的
      • public class Body&lt;T&gt; { private List&lt;T&gt; mylist; } } 喜欢这样吗?
      【解决方案3】:
      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;
         }
      
      }
      

      这并没有真正减少样板代码,但它应该可以防止您拥有一个单独的 body 类,其中的类列表只是您的实际动物的列表。它应该让你只拥有你的身体课程,然后为每只动物提供一个带有它的统计数据的课程。

      【讨论】:

      • 实际上这是一个糟糕的主意,有一次这些List 字段中只有一个字段不为空。想象一下具有大约 20 个这样的动物类和结构的所有错误可能性。
      猜你喜欢
      • 1970-01-01
      • 2015-05-03
      • 2013-08-26
      • 1970-01-01
      • 2014-11-29
      • 1970-01-01
      • 2020-11-19
      • 1970-01-01
      相关资源
      最近更新 更多