Gson使用TypeAdapter或Json Deserializer将数据从错误列表转换为空列表

2023-12-15

让我们从例子开始:

如果数据正确的话应该是(Beijing cities是空的 )

{
   "code":200,
   "msg":"success",
   "data":[
      {
         "id":1,
         "name":"Beijing",
         "cities":[]
      },
      {
         "id":2,
         "name":"Guangdong",
         "cities":[
            {
               "id":1,
               "name":"Guangzhou"
            }
         ]
      }
   ]
}

现在我得到了错误的数据。 ( 这Beijing cities一片空白 )

{
   "code":200,
   "msg":"success",
   "data":[
      {
         "id":1,
         "name":"Beijing",
         "cities":null
      },
      {
         "id":2,
         "name":"Guangdong",
         "cities":[
            {
               "id":1,
               "name":"Guangzhou"
            }
         ]
      }
   ]
}

我正在使用Retrofit2 ResponseBodyConverter,实体类:

public class Result<T> {
    private int code;
    private String msg;
    private T data;

    // getters, setters
}

public class Province {
    private int id;
    private String name;
    private List<City> cities;

}

public class City {
    private int id;
    private String name;

}

The data obtained after deserialization is like this:
enter image description here

but the data I need is like this:
enter image description here

为了有更好的容错能力,当数据是list的时候,我想自己处理一下。
首先,我尝试使用JsonDeserializer

Gson gson = new GsonBuilder()
              .serializeNulls()
              .registerTypeHierarchyAdapter(List.class, new GsonListAdapter())
              .create();

static class GsonListAdapter implements JsonDeserializer<List<?>> {
    @Override
    public List<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        if (json.isJsonArray()) {
            JsonArray array = json.getAsJsonArray();
            Type itemType = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
            List list = new ArrayList<>();
            for (int i = 0; i < array.size(); i++) {
                JsonElement element = array.get(i);
                Object item = context.deserialize(element, itemType);
                list.add(item);
            }
            return list;
        } else {
            return Collections.EMPTY_LIST;
        }
    }
}

JsonDeserializer当数据有效时"", {}, and [],but data is null, 不起作用。

然后我尝试使用TypeAdapter

static class GsonListAdapter extends TypeAdapter<List<?>> {

    @Override
    public void write(JsonWriter out, List<?> value) throws IOException {
        out.value(String.valueOf(value));
    }

    @Override
    public List<?> read(JsonReader reader) throws IOException {
        if (reader.peek() != JsonToken.BEGIN_ARRAY) {
            reader.skipValue();
            return Collections.EMPTY_LIST;
        }
        return new Gson().fromJson(reader, new TypeToken<List<?>>() {}.getType());
    }
}

这样一来,无论发生什么data是的,它可以正常工作。我们知道使用TypeToken<List<?>>会给我们LinkedHashMap,所以虽然TypeAdapter可以正常使用,但不知道如何转换JsonReader to the List <?>.

所以我想知道是否有其他方法可以处理错误的列表数据?或者转换JsonReader to the List <?> data I want.


我找到了CollectionTypeAdapterFactory in Gson源码,我尝试修改了一下,经过测试,很有用。

public class CollectionTypeAdapterFactory implements TypeAdapterFactory {
    private final ConstructorConstructor constructorConstructor;

    public CollectionTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
        this.constructorConstructor = constructorConstructor;
    }

    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        Type type = typeToken.getType();

        Class<? super T> rawType = typeToken.getRawType();
        if (!Collection.class.isAssignableFrom(rawType)) {
            return null;
        }

        Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
        TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
        ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);

        @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
                TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
        return result;
    }

    private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
        private final TypeAdapter<E> elementTypeAdapter;
        private final ObjectConstructor<? extends Collection<E>> constructor;

        public Adapter(Gson context, Type elementType,
                       TypeAdapter<E> elementTypeAdapter,
                       ObjectConstructor<? extends Collection<E>> constructor) {
            this.elementTypeAdapter =
                    new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
            this.constructor = constructor;
        }

        public Collection<E> read(JsonReader in) throws IOException {
            if (in.peek() == JsonToken.NULL) {
                in.nextNull();
                //In the source code is return null, I changed to return an empty collection
                return constructor.construct();
            }

            Collection<E> collection = constructor.construct();
            in.beginArray();
            while (in.hasNext()) {
                E instance = elementTypeAdapter.read(in);
                collection.add(instance);
            }
            in.endArray();
            return collection;
        }

        public void write(JsonWriter out, Collection<E> collection) throws IOException {
            if (collection == null) {
                out.nullValue();
                return;
            }

            out.beginArray();
            for (E element : collection) {
                elementTypeAdapter.write(out, element);
            }
            out.endArray();
        }
    }
}

在源代码中TypeAdapterRuntimeTypeWrapper受保护,我们必须制作一份副本。

  public class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
      private final Gson context;
      private final TypeAdapter<T> delegate;
      private final Type type;

      TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
          this.context = context;
          this.delegate = delegate;
          this.type = type;
      }

      @Override
      public T read(JsonReader in) throws IOException {
          return delegate.read(in);
      }

      @SuppressWarnings({"rawtypes", "unchecked"})
      @Override
      public void write(JsonWriter out, T value) throws IOException {
          TypeAdapter chosen = delegate;
          Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
          if (runtimeType != type) {
              TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
              if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
                  // The user registered a type adapter for the runtime type, so we will use that
                  chosen = runtimeTypeAdapter;
              } else if (!(delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) {
                  // The user registered a type adapter for Base class, so we prefer it over the
                  // reflective type adapter for the runtime type
                  chosen = delegate;
              } else {
                  // Use the type adapter for runtime type
                  chosen = runtimeTypeAdapter;
              }
          }
          chosen.write(out, value);
      }

      private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
          if (value != null
                  && (type == Object.class || type instanceof TypeVariable<?> || type instanceof Class<?>)) {
              type = value.getClass();
          }
          return type;
      }
  }

如何使用

Gson gson = new GsonBuilder().serializeNulls()
           .registerTypeAdapterFactory(
             new CollectionTypeAdapterFactory(new ConstructorConstructor(new HashMap<>()))
             )
           .create();

Result<List<Province>> result = gson.fromJson(jsonStr, new TypeToken<Result<List<Province>>>() {}.getType());

prints:

Result{code=200, msg='success', data=[Province{id=1, name='Beijing', cities=[]}, Province{id=2, name='Guangdong', cities=[City{id=1, name='Guangzhou'}]}]}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Gson使用TypeAdapter或Json Deserializer将数据从错误列表转换为空列表 的相关文章

  • 从 HttpClient 3 转换为 4

    我已经成功地对所有内容进行了更改 但以下内容除外 HttpClient client HttpPost method client new DefaultHttpClient method new HttpPost url InputStr
  • 更改 JComboBox 中滚动条的大小

    有谁知道如何手动更改 jComboBox 中的滚动条大小 我已经尝试了一大堆东西 但没有任何效果 好吧 我明白了 您可以实现 PopUpMenuListener 并使用它 public void popupMenuWillBecomeVis
  • Android 媒体播放器搜索栏

    我有一个创建 播放和处理媒体播放器 只是音频 的服务 但我在主要活动中有一个搜索栏 我想自然地显示音频文件的进度并允许用户搜索到不同的位置 我花了很长时间才弄清楚 将 UI 中的搜索栏连接到服务中的媒体播放器的最佳或正确方法是什么 我将这样
  • 如何使用 MotionLayout 调整 TextView 的大小

    我正在尝试创建一个CollapsingToolbar动画使用MotionLayout 我已经成功地将所有内容设置为动画 使其表现得像CollapsingToolbar具有高度的灵活性 这意味着我可以轻松创建很棒的动画 而无需编写大量代码 我
  • 如何在 Bean Validation 1.0 中构造 ConstraintViolationException?

    我对 javax validation API 感到困惑 我正在编写一个简单的测试来理解它 Sample sample new Sample Set
  • 调整 SwipeRefreshLayout 高度,将 View 置于其底部

    I have SwipeRefreshLayout里面一个RelativeLayout 问题是SwipeRefreshLayout占据了屏幕上的所有位置 我需要放置一个视图after这个观点 看图片 https i stack imgur
  • 改变 Java 中凯撒移位的方向

    用户可以通过选择 1 向左或 2 向右移动字母来选择向左或向右移动 左边工作正常 右边不行 现在它显示了完全相同的循环 但我已经改变了所有 and 以不同的方式进行标记 最终我总是得到奇怪的字符 如何让程序将字符向相反方向移动 如果用户输入
  • 将触摸事件从 NestedScrollView 传递到父视图

    我在 NestedScrollView 下方有一个 ViewPager 宽度一些顶部填充 以及 ClipToPadding false 和透明背景 如图像 我的 ViewPager 无法获取触摸事件并且无法工作 我怎么解决这个问题 我无法更
  • 推特更新状态

    我正在通过 twitter4j 将 Twitter 集成到 Android 我可以成功阅读我发布的推文 现在我试图用它发布推文 但我不能 我收到如下奇怪的警告 02 01 16 28 43 298 WARN System err 729 4
  • 如果我的应用程序安装在 SD 卡上,私人数据也在那里吗?

    我假设应用程序的私有数据 例如 SharedPreferences 和 SQLite 数据库 位于手机的内部存储而不是 SD 卡上 即使应用程序本身安装在 SD 卡上 我在任何地方都找不到对此的简单明确的确认 有人可以确认一下吗 是的 私有
  • Android Jetpack Compose 尺寸随持续时间变化的动画

    如何在 Jetpack Compose 中添加内容大小更改动画的持续时间 尝试使用Modifier animateContentSize 并通过动画规格具有持续时间 但它只是突然进入或退出 没有观察到持续时间 Column Modifier
  • 如何在 IntelliJ IDEA 中按 JSON 中的路径搜索?

    我有很长的 JSON 文件 例如 a b c keyC 和路径 a b c 如何使用路径在 JSON 中搜索 转到行 问题类似于如何在 IntelliJ IDEA 中复制 JSON 中的路径 https stackoverflow com
  • 从浏览器访问本地文件?

    您好 我想从浏览器访问系统的本地文件 由于涉及大量安全检查 是否可以通过某种方式实现这一目标 或使用 ActiveX 或 Java Applet 的任何其他工作环境 请帮帮我 要通过浏览器访问本地文件 您可以使用签名的 Java Apple
  • android 中的 java.net.URL ..新手问题

    我是java新手 正在尝试android开发 以下代码生成 malformedURLException 有人可以帮助我识别异常吗 任何提示都会非常有帮助 package com example helloandroid import and
  • 传递 Android DialogFragment 参数时,onCreateDialog 捆绑参数意外为 null

    我正在尝试使用 DialogFragment 在 Android 中显示一个基本对话框 并使用对话框消息的参数 如中所述StackOverflow线程 https stackoverflow com questions 15459209 p
  • eventSources 到事件 Json,完整日历

    我正在尝试从 eventSources 获取 json 调用到我的事件 我在 eventSources 中返回的 json 是 title Title Test start 1305841052 当我将此字符串传递到事件中时 它会正确显示日
  • 如何减少 Android 中浮动 editText 提示和 editText 框之间的空间?

    我有一个带有浮动提示的 EditText 但我想知道如何减少浮动提示和 EditText 框之间的空间 现在我的用户界面看起来像https i stack imgur com ltfra jpg https i stack imgur co
  • Jackson 反序列化相当于 @JsonUnwrapped 吗?

    假设我有以下课程 public class Parent public int age JsonUnwrapped public Name name 生成 JSON age 18 first Joey last Sixpack 我如何将其反
  • Android Google 地图无法在当前主题中找到样式“mapViewStyle”

    添加谷歌地图视图时 我扩展了MapView 使用xml编辑器将其添加到活动中 并将我的谷歌地图api密钥手动添加到布局xml文件中 我的权限在清单文件中允许互联网 我想知道的是 在 xml 编辑器中 我收到错误 无法在当前主题中找到样式 m
  • 你能快速告诉我这个伪代码是否有意义吗?

    我相信我的代码现在是万无一失的 我现在将写出伪代码 但我确实有一个问题 为什么 DRJava 要求我返回 if 语句之外的内容 正如你所看到的 我为 ex 写了 return 1 只是因为它问了 但是它永远不会返回该值 谁可以给我解释一下这

随机推荐