【问题标题】:Gson deserialising reddit comments API response on androidGson 在 android 上反序列化 reddit 评论 API 响应
【发布时间】:2017-05-03 15:32:16
【问题描述】:

我正在 Android 上构建一个 reddit 客户端,使用 Retrofit 和 Gson 来发出 API 请求。我请求检索帖子的 cmets

https://www.reddit.com/r/pics/comments/68pxct/black_out_panels_with_a_twist/.json

在反序列化我遇到问题的响应时,响应有一个字段children,它有时会返回List<CustomObject>,有时会返回List<String>

我尝试了多个选项,使用 JsonAdapter 和自定义 JsonDeserializer,但不起作用,然后尝试使用 TypeAdapter,仍然不起作用。

我的领域是这样的,

@Expose
@JsonAdapter(CustomDeserializer.class)
private List<T> children;

在这两种情况下,我都遇到了与泛型一起使用的类的类未找到异常。我在反序列化器中有断点,它甚至没有开始执行代码。注释 @JsonAdapter 注释导致类未找到异常消失。

然后我尝试将参数接收到通用 JsonArray 类型中,但仍然遇到错误

@Expose
private JsonArray children;

原因:java.lang.IllegalStateException:预期为 BEGIN_OBJECT,但在第 1 行第 1 列 210200 路径 $[1].data.children[12].data.replies 为 STRING

为什么我无法将其转换为 JsonArray。我认为无论内部类型是什么都应该有效。

理想情况下,我想让 JsonAdapter 方法工作。

编辑:

将其转换为 JsonArry 不起作用的原因是因为有另一个字段 (replies) 也返回了多种数据类型。看到错误应该很明显,我想它已经过了我的就寝时间。因此,将字段转换为 JsonArray 和 JsonObject 就可以了。

也有人建议泛型与 JsonAdapter 注释不能很好地配合,我将测试其他解决方案并更新。

【问题讨论】:

  • 您数据中的replies 以JSON 中的引号开头,而不是对象{}
  • 您可以在 JSON 中搜索 "replies": ""
  • @cricket_007 没错,回复确实是这样开始的。但是,由于我将子对象反序列化为 JsonArray,这不重要吗?
  • 我不确定。你可以试试minimal reproducible example吗?

标签: android gson retrofit android-json


【解决方案1】:

你不能使用&lt;T&gt;,因为 Gson 无法为深度嵌套的子对象检索足够的类型信息。您可以做的是将“自定义对象”和字符串对齐在一起的一种对齐方式,以便您可以轻松控制这两种类型。

假设你可能有这样的事情:

// Not an interface by design: it's most likely there is just two known data types
abstract class Element {

    // So we can control they instantiation
    private Element() {
    }

    // ... any convenient code, visitor pattern stuff here, etc ..

    static Element reference(final String reference) {
        return new ReferenceElement(reference);
    }

    static final class DataElement
            extends Element {

        final String kind = null;
        final Data data = null;

        // Gson does requires neither constructors nor making them non-private
        private DataElement() {
        }

    }

    // This is a special wrapper because we cannot make java.util.String to be a subclass of the Element class
    // Additionally, you can add more methods if necessary
    static final class ReferenceElement
            extends Element {

        final String reference;

        // But anyway, control the way it's instantiated within the enclosing class
        private ReferenceElement(final String reference) {
            this.reference = reference;
        }

    }

}

由于我不熟悉 Reddit API,我假设它可能会映射到以下类以获取特定响应:

final class Data {

    final List<Element> children = null;
    final Element replies = null;

}
final class ElementTypeAdapterFactory
        implements TypeAdapterFactory {

    // Effectively a singleton, no state, fully thread-safe, etc    
    private static final TypeAdapterFactory elementTypeAdapterFactory = new ElementTypeAdapterFactory();

    private static final TypeToken<DataElement> dateElementTypeToken = new TypeToken<DataElement>() {
    };

    private ElementTypeAdapterFactory() {
    }

    // So just return the single instance and hide away the way it's instantiated
    static TypeAdapterFactory getElementTypeAdapterFactory() {
        return elementTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Not the Element class?
        if ( !Element.class.isAssignableFrom(typeToken.getRawType()) ) {
            // Then just let Gson pick up the next best type adapter
            return null;
        }
        //
        final TypeAdapter<DataElement> dataElementTypeAdapter = gson.getDelegateAdapter(this, dateElementTypeToken);
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) new ElementTypeAdapter(dataElementTypeAdapter);
        return typeAdapter.nullSafe();
    }

    private static final class ElementTypeAdapter
            extends TypeAdapter<Element> {

        private final TypeAdapter<DataElement> dataTypeAdapter;

        private ElementTypeAdapter(final TypeAdapter<DataElement> dataTypeAdapter) {
            this.dataTypeAdapter = dataTypeAdapter;
        }

        @Override
        public void write(final JsonWriter out, final Element value)
                throws IOException {
            if ( value instanceof DataElement ) {
                dataTypeAdapter.write(out, (DataElement) value);
            } else if ( value instanceof ReferenceElement ) {
                out.value(((ReferenceElement) value).reference);
            } else {
                // null-protection is configured with .nullSafe() above
                throw new AssertionError(value.getClass());
            }
        }

        @Override
        public Element read(final JsonReader in)
                throws IOException {
            final JsonToken token = in.peek();
            switch ( token ) {
            case BEGIN_OBJECT:
                return dataTypeAdapter.read(in);
            case STRING:
                return reference(in.nextString());
            case BEGIN_ARRAY:
            case END_ARRAY:
            case END_OBJECT:
            case NAME:
            case NUMBER:
            case BOOLEAN:
            case NULL: // null-protection is configured with .nullSafe() above
            case END_DOCUMENT:
                throw new MalformedJsonException("Cannot parse " + token + " at " + in);
            default:
                // If someday there are more tokens...
                throw new AssertionError(token);
            }
        }

    }

}

现在把这些放在一起:

    private static final Type type = new TypeToken<List<Element>>() {
    }.getType();

    private static final Gson gson = new GsonBuilder()
            .registerTypeAdapterFactory(getElementTypeAdapterFactory())
            .create();

    public static void main(final String... args)
            throws IOException {
        try ( final Reader reader = getPackageResourceReader(Q43764362.class, "reddit.json") ) {
            final List<Element> elements = gson.fromJson(reader, type);
            dump(elements);
        }
    }

    private static void dump(final Iterable<Element> abstractElements) {
        dump(abstractElements, 0);
    }

    private static void dump(final Iterable<Element> abstractElements, final int level) {
        final String tab = repeat(".", level);
        for ( final Element e : abstractElements ) {
            if ( e instanceof DataElement ) {
                final DataElement dataElement = (DataElement) e;
                System.out.print(tab);
                System.out.print("DATA=");
                System.out.println(dataElement.kind);
                if ( dataElement.data.children != null ) {
                    dump(dataElement.data.children, level + 1);
                }
                if ( dataElement.data.replies != null ) {
                    final Element replies = dataElement.data.replies;
                    if ( dataElement.data.replies instanceof DataElement ) {
                        dump(((DataElement) replies).data.children, level + 1);
                    } else if ( dataElement.data.replies instanceof ReferenceElement ) {
                        System.out.print(tab);
                        System.out.print("REF=");
                        System.out.println(((ReferenceElement) dataElement.data.replies).reference);
                    } else {
                        throw new AssertionError(replies.getClass());
                    }
                }
            } else if ( e instanceof ReferenceElement ) {
                System.out.print(tab);
                System.out.print("REF=");
                System.out.println(((ReferenceElement) e).reference);
            } else {
                throw new IllegalArgumentException(String.valueOf(e));
            }
        }
    }

当前响应的输出摘录:

DATA=列表
.DATA=t3
DATA=列表
.DATA=t1
..DATA=t1
...数据=t1
....DATA=t1
.....数据=t1
......数据=t1
.......数据=更多
........REF=dh0x8h5
.....数据=更多
......REF=dh11148
......REF=dh19yft

..REF=dh0pcjh
..REF=dh0n73y
..REF=dh0kp1r
..REF=dh0mg9c
..REF=dh0i6z5
..REF=dh0inc3
..REF=dh0oyc4
..REF=dh0phb0
..REF=dh0ln22
..REF=dh0wjqa
..REF=dh0q48s
..REF=dh0tfjl
..REF=dh0kauq
..REF=dh0rxtf
..REF=dh0led3

【讨论】:

  • 谢谢。您的解决方案看起来非常适合我的需要。我将两个字段都更改为 JsonObject 和 JsonArray 类型,它现在可以工作,我今天会测试一下。
猜你喜欢
  • 2021-03-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-20
  • 2011-12-19
  • 2014-03-02
相关资源
最近更新 更多