com.birbit.jsonapi.JsonApiDeserializer.java Source code

Java tutorial

Introduction

Here is the source code for com.birbit.jsonapi.JsonApiDeserializer.java

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.birbit.jsonapi;

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

@SuppressWarnings("WeakerAccess")
public class JsonApiDeserializer<T> implements JsonDeserializer<JsonApiResponse> {

    Map<String, JsonApiResourceDeserializer<?>> deserializerMap;
    final Map<Class, String> typeMapping;

    public JsonApiDeserializer(JsonApiResourceDeserializer... deserializers) {
        deserializerMap = new HashMap<String, JsonApiResourceDeserializer<?>>((int) (deserializers.length * 1.25));
        typeMapping = new HashMap<Class, String>();
        for (JsonApiResourceDeserializer deserializer : deserializers) {
            deserializerMap.put(deserializer.apiType, deserializer);
            String previous = typeMapping.put(deserializer.klass, deserializer.apiType);
            if (previous != null) {
                throw new IllegalArgumentException("multiple types map to klass " + deserializer.klass
                        + ". This is "
                        + "not supported. To workaround it, you can create a class that extends the other one. "
                        + "Conflicting types:" + previous + ", " + deserializer.apiType);
            }
        }

    }

    public JsonApiResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
            throws JsonParseException {
        if (!json.isJsonObject()) {
            throw new JsonParseException("JSON API response root should be a json object");
        }
        if (!(typeOfT instanceof ParameterizedType)) {
            throw new JsonParseException("JSON API response should be requested with a parameterized type where the"
                    + " type parameter represents the `data` field's type");
        }
        ParameterizedType parameterizedType = (ParameterizedType) typeOfT;
        JsonObject jsonObject = json.getAsJsonObject();

        JsonApiLinks links = parseLinks(context, jsonObject);

        T[] data = parseData(context, parameterizedType, jsonObject);
        List<JsonApiError> errors = parserErrors(context, jsonObject);
        if ((data == null) == (errors == null)) {
            throw new JsonParseException("The JSON API response should have data or errors");
        }
        if (errors != null) {
            return new JsonApiResponse(errors, typeMapping, links);
        }
        Map<String, Map<String, Object>> included = parseIncluded(context, jsonObject);
        //noinspection unchecked
        return new JsonApiResponse(data, included, typeMapping, links);
    }

    private List<JsonApiError> parserErrors(JsonDeserializationContext context, JsonObject jsonObject) {
        JsonElement errors = jsonObject.get("errors");
        if (errors == null || !errors.isJsonArray()) {
            return null;
        }
        JsonArray asJsonArray = errors.getAsJsonArray();
        int size = asJsonArray.size();
        List<JsonApiError> result = new ArrayList<JsonApiError>(size);
        for (int i = 0; i < size; i++) {
            result.add(context.<JsonApiError>deserialize(asJsonArray.get(i), JsonApiError.class));
        }
        return result;
    }

    private JsonApiLinks parseLinks(JsonDeserializationContext context, JsonObject jsonObject) {
        JsonElement links = jsonObject.get("links");
        if (links == null || !links.isJsonObject()) {
            return JsonApiLinks.EMPTY;
        }
        return context.deserialize(links, JsonApiLinks.class);
    }

    private Map<String, Map<String, Object>> parseIncluded(JsonDeserializationContext context,
            JsonObject jsonObject) {
        JsonElement includedElm = jsonObject.get("included");
        Map<String, Map<String, Object>> included;
        if (includedElm != null && includedElm.isJsonArray()) {
            included = new HashMap<String, Map<String, Object>>();
            JsonArray includedArray = includedElm.getAsJsonArray();
            final int size = includedArray.size();
            for (int i = 0; i < size; i++) {
                ResourceWithIdAndType parsed = parseResource(includedArray.get(i), context);
                if (parsed.resource != null) {
                    Map<String, Object> itemMap = included.get(parsed.apiType);
                    if (itemMap == null) {
                        itemMap = new HashMap<String, Object>();
                        included.put(parsed.apiType, itemMap);
                    }
                    itemMap.put(parsed.id, parsed.resource);
                }
            }
        } else {
            included = Collections.emptyMap();
        }
        return included;
    }

    private T[] parseData(JsonDeserializationContext context, ParameterizedType parameterizedType,
            JsonObject jsonObject) {
        JsonElement dataElm = jsonObject.get("data");
        if (dataElm != null) {
            Type typeArg = parameterizedType.getActualTypeArguments()[0];
            if (dataElm.isJsonArray()) {
                JsonArray jsonArray = dataElm.getAsJsonArray();
                final int size = jsonArray.size();
                //                boolean isArray = typeArg instanceof GenericArrayType;
                //                if (isArray) {
                TypeToken<?> typeToken = TypeToken.get(typeArg);
                T[] result = (T[]) Array.newInstance(typeToken.getRawType().getComponentType(), size);
                for (int i = 0; i < size; i++) {
                    ResourceWithIdAndType<T> resourceWithIdAndType = parseResource(jsonArray.get(i), context);
                    result[i] = resourceWithIdAndType.resource;
                }
                return result;
                //                } else {
                //                    TypeToken<?> typeToken = TypeToken.get(typeArg);
                //                    T[] result = (T[]) Array.newInstance(typeToken.getRawType().getComponentType(), size);
                //                    for (int i = 0; i < size; i ++) {
                //                        ResourceWithIdAndType<T> resourceWithIdAndType = parseResource(jsonArray.get(i), context);
                //                        //noinspection unchecked
                //                        result[i] = resourceWithIdAndType.resource;
                //                    }
                //                    return result;
                //                }
            } else if (dataElm.isJsonObject()) {
                T resource = parseResource(dataElm, context).resource;
                Object[] result = (Object[]) Array.newInstance(resource.getClass(), 1);
                result[0] = resource;
                return (T[]) result;
            }
        }
        return null;
    }

    private ResourceWithIdAndType<T> parseResource(JsonElement jsonElement, JsonDeserializationContext context) {
        JsonObject jsonObject = jsonElement.getAsJsonObject();
        String apiType = jsonObject.get("type").getAsString();
        String id = jsonObject.get("id").getAsString();
        JsonApiResourceDeserializer<?> deserializer = deserializerMap.get(apiType);
        Object resource;
        if (deserializer != null) {
            resource = deserializer.deserialize(id, jsonElement, context);
        } else {
            resource = null;
        }
        return new ResourceWithIdAndType(apiType, id, resource);
    }

    public static GsonBuilder register(GsonBuilder builder, JsonApiResourceDeserializer... deserializers) {
        return builder.registerTypeAdapter(JsonApiResponse.class, new JsonApiDeserializer(deserializers))
                .registerTypeAdapter(JsonApiLinks.class, JsonApiLinksDeserializer.INSTANCE);
    }

    static class ResourceWithIdAndType<T> {
        final String apiType;
        final String id;
        final T resource;

        public ResourceWithIdAndType(String apiType, String id, T resource) {
            this.apiType = apiType;
            this.id = id;
            this.resource = resource;
        }
    }
}