使用TypeAdapter在Android上进行Retrofit的ARRAY或OBJECT,在两个深度级

时间:2017-04-27 10:00:17

标签: android gson retrofit2

我正在努力使用TypeAdapter。实际上,对于json字段,我可以有一个Array(当它为空时)或一个Object(当它不为空时)。这不能改变。

以下是收到的JSON:

{
    "notifications": [
        {
            ...
        }
    ],
    "meta": {
        "pagination": {
            "total": 13,
            "count": 13,
            "per_page": 20,
            "current_page": 1,
            "total_pages": 1,
            "links": []
        }
    }
}

相关字段为links,因为您可以看到该字段位于paginationmeta。这就是我的问题,我不知道TypeAdapter如何在两个深度级别处理links

我使用此reply开始构建解决方案。这是:

我的自定义TypeAdapter类:

public class PaginationTypeAdapter extends TypeAdapter<Pagination> {

    private Gson gson = new Gson();

    @Override
    public void write(JsonWriter out, Pagination pagination) throws IOException {
        gson.toJson(pagination, Links.class, out);
    }

    @Override
    public Pagination read(JsonReader jsonReader) throws IOException {

        Pagination pagination;

        jsonReader.beginObject();

        if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
            pagination = new Pagination((Links[]) gson.fromJson(jsonReader, Links[].class));
        } else if(jsonReader.peek() == JsonToken.BEGIN_OBJECT) {
            pagination = new Pagination((Links) gson.fromJson(jsonReader, Links.class));
        } else {
            throw new JsonParseException("Unexpected token " + jsonReader.peek());
        }

        return pagination;
    }
}

My Pagination课程:

public class Pagination {

    private int total;

    private int count;

    @SerializedName("per_page")
    private int perPage;

    @SerializedName("current_page")
    private int currentPage;

    @SerializedName("total_pages")
    private int totalPages;

    private Links links;


    Pagination(Links ... links) {
        List<Links> linksList = Arrays.asList(links);
        this.links = linksList.get(0);
    }
}

我正在构建我的Gson对象:

Gson gson = new GsonBuilder().registerTypeAdapter(Pagination.class, new PaginationTypeAdapter()).create();

现在我的错误是:com.google.gson.JsonParseException: Unexpected token NAME

所以我知道我做得不对,因为我正在用分页来构建我的Gson。但我不知道应该如何处理。使用带有meta的TypeAdapter?

欢迎任何帮助,谢谢!

1 个答案:

答案 0 :(得分:1)

当您实现自定义类型适配器时,请确保您的类型适配器具有平衡的令牌读写:如果您打开复合令牌对(如[]),则必须将其关闭(适用于JsonWriterJsonReader)。您只是不需要这一行来解决您的问题:

jsonReader.beginObject();

因为它将JsonReader实例移动到下一个标记,因此BEGIN_OBJECT之后的下一个标记是NAMEEND_OBJECT(前者在您的情况下确定)。

备选方案#1

我建议不要使用临时Gson对象实例 - 这不会在Gson实例之间共享配置(例如,您的&#34;全局&#34; Gson注册了很多自定义适配器,但是这个内部没有任何自适应,因此你的(反)序列化结果可能会非常意外。为了克服这一点,只需使用比{&#34; free&#34;更具上下文感知能力的TypeAdapterFactoryGson实例。

final class PaginationTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory paginationTypeAdapterFactory = new PaginationTypeAdapterFactory();

    private PaginationTypeAdapterFactory() {
    }

    static TypeAdapterFactory getPaginationTypeAdapterFactory() {
        return paginationTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Classes can be compared using == and !=
        if ( typeToken.getRawType() != Pagination.class ) {
            // Not Pagination? Let Gson pick up the next best-match
            return null;
        }
        // Here we get the references for two types adapters:
        // - this is what Gson.fromJson does under the hood
        // - we save some time for the further (de)serialization
        // - you classes should not ask more than they require
        final TypeAdapter<Links> linksTypeAdapter = gson.getAdapter(Links.class);
        final TypeAdapter<Links[]> linksArrayTypeAdapter = gson.getAdapter(Links[].class);
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) new PaginationTypeAdapter(linksTypeAdapter, linksArrayTypeAdapter);
        return typeAdapter;
    }

    private static final class PaginationTypeAdapter
            extends TypeAdapter<Pagination> {

        private final TypeAdapter<Links> linksTypeAdapter;
        private final TypeAdapter<Links[]> linksArrayTypeAdapter;

        private PaginationTypeAdapter(final TypeAdapter<Links> linksTypeAdapter, final TypeAdapter<Links[]> linksArrayTypeAdapter) {
            this.linksTypeAdapter = linksTypeAdapter;
            this.linksArrayTypeAdapter = linksArrayTypeAdapter;
        }

        @Override
        public void write(final JsonWriter out, final Pagination pagination)
                throws IOException {
            linksTypeAdapter.write(out, pagination.links);
        }

        @Override
        public Pagination read(final JsonReader in)
                throws IOException {
            final JsonToken token = in.peek();
            // Switches are somewhat better: you can let your IDE or static analyzer to check if you covered ALL the cases
            switch ( token ) {
            case BEGIN_ARRAY:
                return new Pagination(linksArrayTypeAdapter.read(in));
            case BEGIN_OBJECT:
                return new Pagination(linksTypeAdapter.read(in));
            case END_ARRAY:
            case END_OBJECT:
            case NAME:
            case STRING:
            case NUMBER:
            case BOOLEAN:
            case NULL:
            case END_DOCUMENT:
                // MalformedJsonException, not sure, might be better, because it's an IOException and the read method throws IOException
                throw new MalformedJsonException("Unexpected token: " + token + " at " + in);
            default:
                // Maybe some day Gson adds something more here... Let be prepared
                throw new AssertionError(token);
            }
        }
    }

}

备选方案#2

您可以使用private Links links;@JsonAdapter进行注释,并将类型适配器工厂直接绑定到链接:Gson将&#34;注入&#34; links直接反对Pagination个实例,因此您甚至不需要构造函数。