如何在Android上的Retrofit中处理可能是ARRAY或OBJECT的参数?

时间:2014-10-22 05:21:57

标签: android json retrofit

我遇到的问题是我正在解析的API会返回一个大小为1的ARRAY的OBJECT。

例如,有时API会响应:

{
    "monument": [
        {
            "key": 4152,
            "name": "MTS - Corporate Head Office",
            "categories": {},
            "address": {}
        },
        {
            "key": 4151,
            "name": "Canadian Transportation Agency",
            "categories": {},
            "address": {}
        },
        {
            "key": 4153,
            "name": "Bank of Montreal Building",
            "categories": {},
            "address": {}
        }
    ],
}

但是,如果monument数组只有1个项目,则它变为OBJECT(注意缺少[]括号),如下所示:

{
    "monument": {
        "key": 4152,
        "name": "MTS - Corporate Head Office",
        "categories": {},
        "address": {}
    }
}

如果我这样定义我的模型,只返回一个项目时会出错:

public class Locations {
    public List<Monument> monument;
}

如果只返回一个项目,我会收到以下错误:

Expected BEGIN_OBJECT but was BEGIN_ARRAY ...

如果我这样定义我的模型:

public class Locations {
    public Monument monument;
}

并且API返回ARRAY我得到相反的错误

Expected BEGIN_ARRAY  but was BEGIN_OBJECT ...

我无法在模型中定义多个具有相同名称的项目。 我怎么处理这个案子?

注意:我无法对API进行更改。

3 个答案:

答案 0 :(得分:11)

诀窍是 为您的Locations课程编写自己的Gson反序列化程序 。这将检查纪念碑元素是对象还是数组。像这样:

public class LocationsDeserializer implements JsonDeserializer<Locations> {

    @Override
    public Locations deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        JsonElement monumentElement = json.getAsJsonObject().get("monument");
        if (monumentElement.isJsonArray()) {
            return new Locations((Monument[]) context.deserialize(monumentElement.getAsJsonArray(), Monument[].class));
        } else if (monumentElement.isJsonObject()) {
            return new Locations((Monument) context.deserialize(monumentElement.getAsJsonObject(), Monument.class));
        } else {
            throw new JsonParseException("Unsupported type of monument element");
        }
    }
}

为方便起见,将vararg构造函数添加到Locations类:

public class Locations {
    public List<Monument> monuments;

    public Locations(Monument ... ms) {
        monuments = Arrays.asList(ms);
    }
}

你的纪念碑课程保持不变。类似的东西:

public class Monument {
    public int key;
    public String name;
    // public Categories categories;
    // public Address address;
}

最后,创建自己的Gson对象并将其传递给改造版RestAdapter

Gson gson = new GsonBuilder().registerTypeAdapter(Locations.class, new LocationsDeserializer()).create();

RestAdapter restAdapter = new RestAdapter.Builder()
            .setEndpoint(baseUrl)
            .setConverter(new GsonConverter(gson))
            .build();

答案 1 :(得分:11)

作为我之前回答的补充,这是使用TypeAdapter的解决方案。

public class LocationsTypeAdapter extends TypeAdapter<Locations> {

    private Gson gson = new Gson();

    @Override
    public void write(JsonWriter jsonWriter, Locations locations) throws IOException {
        gson.toJson(locations, Locations.class, jsonWriter);
    }

    @Override
    public Locations read(JsonReader jsonReader) throws IOException {
        Locations locations;

        jsonReader.beginObject();
        jsonReader.nextName();       

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

        jsonReader.endObject();
        return locations;
    }
}

答案 2 :(得分:0)

您可以使用Gson注册TypeAdapter,有条件地处理此行为。

您先拨打peekToken()。如果是BEGIN_ARRAY,那么只需将其反序列化为List<Foo>。如果是BEGIN_OBJECT,则将其反序列化为Foo并换入Collections.singletonList

现在你总是有一个列表,无论是单项还是多项。