Retrofit: how to parse a JSON array that combines an array and an object?

后端 未结 2 1126
没有蜡笔的小新
没有蜡笔的小新 2021-01-07 12:51

I am working on an Android app that uses Retrofit+OkHttp to connect to a REST API and consume JSON data. I\'m fairly new to Retrofit, so I\'m still learning how it works, bu

2条回答
  •  清歌不尽
    2021-01-07 13:19

    You seem to have not use List because you have to create at least two kinds of objects: "single" and "multiple" where the latter must implement List to satisfy the array-like interface.

    For example. JsonResponse is simpler than what jsonschema2pojo generates:

    final class JsonResponse {
    
        final boolean success = Boolean.valueOf(false);
        final String message = null;
        final List data = null;
    
    }
    

    Now, MyObject can look like this:

    abstract class MyObject
            implements Parcelable {
    
        private MyObject() {
        }
    
        static  MyObject multiple(final List list) {
            return new MultipleObjects<>(list);
        }
    
        static final class SingleObject
                extends MyObject {
    
            private SingleObject() {
            }
    
            final String info = null;
            final String info2 = null;
            final String info3 = null;
    
        }
    
        static final class MultipleObjects
                extends MyObject
                implements List {
    
            private final List list;
    
            private MultipleObjects(final List list) {
                this.list = list;
            }
    
            // @formatter:off
            @Override public int size() { return list.size(); }
            @Override public boolean isEmpty() { return list.isEmpty(); }
            @Override public boolean contains(final Object o) { return list.contains(o); }
            @Override public Iterator iterator() { return list.iterator(); }
            @Override public Object[] toArray() { return list.toArray(); }
            @Override public  T[] toArray(final T[] a) { return list.toArray(a); }
            @Override public boolean add(final E e) { return list.add(e); }
            @Override public boolean remove(final Object o) { return list.remove(o); }
            @Override public boolean containsAll(final Collection c) { return list.containsAll(c); }
            @Override public boolean addAll(final Collection c) { return list.addAll(c); }
            @Override public boolean addAll(final int index, final Collection c) { return list.addAll(index, c); }
            @Override public boolean removeAll(final Collection c) { return list.removeAll(c); }
            @Override public boolean retainAll(final Collection c) { return list.retainAll(c); }
            @Override public void clear() { list.clear(); }
            @Override public E get(final int index) { return list.get(index); }
            @Override public E set(final int index, final E element) { return list.set(index, element); }
            @Override public void add(final int index, final E element) { list.add(index, element); }
            @Override public E remove(final int index) { return list.remove(index); }
            @Override public int indexOf(final Object o) { return list.indexOf(o); }
            @Override public int lastIndexOf(final Object o) { return list.lastIndexOf(o); }
            @Override public ListIterator listIterator() { return list.listIterator(); }
            @Override public ListIterator listIterator(final int index) { return list.listIterator(index); }
            @Override public List subList(final int fromIndex, final int toIndex) { return list.subList(fromIndex, toIndex); }
            // @formatter:on
    
        }
    
    }
    

    The class above implements an abstract class that can be implemented in two ways. Note that no public constructors are exposed by design: SingleObject can be deserialized using Gson really easy using the reflective strategy, whilst MultipleObjects is an array-like object that requires some manual construction.

    The deserialization part:

    final class MyObjectJsonDeserializer
            implements JsonDeserializer {
    
        private static final JsonDeserializer myObjectJsonDeserializer = new MyObjectJsonDeserializer();
    
        // You have to detect it more accurately yourself   
        private static final Type genericListType = new TypeToken>() {
        }.getType();
    
        private MyObjectJsonDeserializer() {
        }
    
        static JsonDeserializer getMyObjectJsonDeserializer() {
            return myObjectJsonDeserializer;
        }
    
        @Override
        public MyObject deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
                throws JsonParseException {
            if ( jsonElement.isJsonNull() ) {
                return null;
            }
            if ( jsonElement.isJsonObject() ) {
                // Note that the deserialization is implemented using context,
                // because it makes sure that you are using the Gson instance configuration
                // Simply speaking: do not create gson instances in 99,9% cases
                return context.deserialize(jsonElement, MyObject.SingleObject.class);
            }
            if ( jsonElement.isJsonArray() ) {
                return multiple(context.deserialize(jsonElement, genericListType));
            }
            // Or create a more sophisticated detector... Or redesign your mappigns
            if ( jsonElement.isJsonPrimitive() ) {
                throw new JsonParseException("Cannot parse primitives");
            }
            throw new AssertionError(jsonElement);
        }
    
    }
    

    Example use:

    private static final Gson gson = new GsonBuilder()
            .registerTypeAdapter(MyObject.class, getMyObjectJsonDeserializer())
            .create();
    
    public static void main(final String... args)
            throws IOException {
        try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43946453.class, "polymorphic.json") ) {
            final JsonResponse response = gson.fromJson(jsonReader, JsonResponse.class);
            for ( final MyObject datum : response.data ) {
                System.out.println(datum.getClass().getSimpleName());
            }
        }
    }
    

    Output:

    MultipleObjects
    SingleObject

提交回复
热议问题