如何使用定制的Gson解串器分析嵌套的JSON数组对象? [英] How do I parse a nested JSON array of object with a custom Gson deserializer?

查看:112
本文介绍了如何使用定制的Gson解串器分析嵌套的JSON数组对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

祝你一切顺利。我在理解这一点时遇到了一些麻烦。我有一个JSON,如下所示:

  {
data:[
{
id:43,
type:position,
attributes:{
address-id:1,
雇主ID:11
}
}
],
包括:[
{
id:1,
type:address,
attributes:{
line-1:21 london london,
line-2:,
line-3:,
locality:,
region:London,
post-code:,
country:UK,
latitude:,
longitude:
}
},
{
id:11,
type:employer,
attributes:{
title:Mr,
名称:S,
last-name: T
}
}
]
}

我的Retrofit调用是:

  @GET(/ api / positions)
Single< PositionResponse>为getPosition();

PositionResponse class:

public class PositionResponse {

@SerializedName(data)
@Expose
private List< DataResponse>数据;
@SerializedName(包含)
@Expose
私人列表< IncludedModel>包括在内;

公共列表< DataResponse> getData(){
返回数据;
}

public void setData(List< DataResponse> data){
this.data = data;
}

公共列表< IncludedModel> getIncluded(){
包括返回;
}

public void setIncluded(包含List< IncludedModel>){
this.included = included;
}

}
}

它有更多的数据。我如何创建一个自定义 TypeAdapter JsonDeserializer 来解析 List< IncludedModel> code>?出于某种原因,我可以为 Object 创建一个自定义 JsonDeserializer TypeAdapter $ c>,但是当涉及到一个 List 时,我似乎无法让它工作。


$ b 我的 TypeAdapter 如下:

  public class IncludedTypeAdapter extends TypeAdapter< ArrayList< IncludedModel>> {
$ b @Override
public void write(JsonWriter out,ArrayList< IncludedModel> value)throws IOException {

}

@Override
public ArrayList< IncludedModel> read(JsonReader in)抛出IOException {
ArrayList< IncludedModel> list = new ArrayList<>();
IncludedModel模型=新的IncludedModel();
Gson gson = new Gson();
in.beginArray();
String id = null;
//in.beginObject();
while(in.hasNext()){
JsonToken nextToken = in.peek();

if(JsonToken.BEGIN_OBJECT.equals(nextToken)){
in.beginObject();

} else if(JsonToken.NAME.equals(nextToken)){

if(JsonToken.NAME.name()。equals(id)){
id = in.nextString();
model.setId(id);

} else if(JsonToken.NAME.name()。equals(type)){
String type = in.nextString();
model.setMytype(type);

switch(type){
case BaseModelType.Employer:
EmployerResponse employer = gson.fromJson(in,EmployerResponse.class);
model.setEmployer(employer);
休息;
}
}
}
}
list.add(model);

返回列表;
}

我注册我的Gson:

  GsonBuilder gsonBuilder = new GsonBuilder(); 
gsonBuilder.registerTypeAdapter(IncludeModel.class,new IncludedTypeAdapter());
//gsonBuilder.registerTypeAdapter(new IncludedTypeAdapter());
gsonBuilder.serializeNulls();
Gson gson = gsonBuilder.create();

返回gson;

我通过 GsonConverterFactory 进行改造注册。



我得到:


期望的BEGIN_ARRAY,但在第1行的BEGIN_OBJECT列6292 path $ .included [0]


我怀疑是因为我的Retrofit响应是< PositionResponse> 这是一个 JsonObject



总结我的问题:如何反序列化使用我自己的自定义类型适配器列出< IncludeModel> 对象,记住我的Retrofit服务的响应类型是 PositionResponse ?非常感谢您的患者和答复。

使用使用JSON树模型很容易, JsonDeserializer 。纯粹的类型适配器有点矫枉过正(以及 RuntimeTypeAdapterFactory 是我认为的,因为它仍然是面向树的),而对于你的JSON文档最简单的情况,你可以使用类似的东西(你可以找到类似的方法在我昨天的回答有更多的解释,但你的情况略有不同)。

我假设你想要有这样的映射:

 抽象类元素{

final String id = null;

private Element(){
}

static final class Address
extends Element {

@SerializedName(line -1)final String line1 = null;
@SerializedName(line-2)final String line2 = null;
@SerializedName(line-3)final String line3 = null;
@SerializedName(locality)final String locality = null;
@SerializedName(region)final String region = null;
@SerializedName(post-code)final String postCode = null;
@SerializedName(country)final String country = null;
@SerializedName(latitude)final String latitude = null;
@SerializedName(longitude)final String longitude = null;
$ b $ private address(){
}

@Override
public String toString(){
return country ++ region;
}



static final class雇主
extends Element {

@SerializedName(title)final String title = null;
@SerializedName(first-name)final String firstName = null;
@SerializedName(last-name)final String lastName = null;
$ b私人雇主(){
}

@Override
public String toString(){
return title +''+ firstName + ''+ lastName;



$ b static final class Position
extends Element {

@SerializedName(address-id)最终字符串addressId = null;
@SerializedName(employer-id)final String employerId = null;

private Position(){
}

@Override
public String toString(){
return'('+ addressId +' ;'+ employerId +')';
}

}

}

您只需要:


  • 确定预期的对象类型;
  • 对齐JSON树(如果它符合你的需求);
  • 通过反序列化上下文将反序列化工作委托给Gson(你的例子不太好:你正在实例化 Gson 再一次丢失了原始配置;你重做所有Gson可以做的不足:列表和POJO通过反射; JsonToken 如果通过 switch (通过 enum s是单例,并且使用引用比较它们是完全合法的等于 == )等。)



通过类似这样的事情:

final class ElementJsonDeserializer
实现JsonDeserializer< Element> {

private static final JsonDeserializer< Element> elementJsonDeserializer = new ElementJsonDeserializer();

private ElementJsonDeserializer(){
}

static JsonDeserializer< Element> getElementJsonDeserializer(){
return elementJsonDeserializer;

$ b @Override
public元素反序列化(final JsonElement jsonElement,final Type类型,final JsonDeserializationContext上下文)
抛出JsonParseException {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
final String typeCode = jsonObject.getAsJsonPrimitive(type)。getAsString();
final Class <?扩展Element> clazz中;
switch(typeCode){
caseaddress:
clazz = Element.Address.class;
休息;
caseemployer:
clazz = Element.Employer.class;
休息;
caseposition:
clazz = Element.Position.class;
休息;
默认值:
抛出新的JsonParseException(Unrecognized type:+ typeCode);
}
reattach(jsonObject,attributes);
return context.deserialize(jsonElement,clazz);


private static void reattach(final JsonObject parent,final String property){
final JsonObject child = parent.getAsJsonObject(property);
parent.remove(property); //在确定它是JSON对象后移除
copyTo(parent,child);

$ b $ private static void copyTo(final JsonObject to,final JsonObject from){
for(final Entry< String,JsonElement> e:from.entrySet()){
to.add(e.getKey(),e.​​getValue());
}
}

}

当然,您可以重构上述内容以提取策略来实施策略设计模式以重用它。把它放在一起:
$ b

final class Response {

final List< ;组件> data = null;
final List< Element>包括= null;

$ b $ / code>

(上面的一个看起来像一个 Map< String,List< Element>> 但您决定)。 $ b

  private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Element.class,getElementJsonDeserializer())
.create();

public static void main(final String ... args)
throws IOException {
try(final JsonReader jsonReader = getPackageResourceJsonReader(Q43811168.class,data.json)) {
final Response response = gson.fromJson(jsonReader,Response.class);
dump(response.data);
dump(response.included);



private final void void dump(final Iterable< Element> elements){
for(final Element e:elements){
System。的out.print(e.getClass()getSimpleName());
System.out.print(#);
System.out.print(e.id);
System.out.print(:);
System.out.println(e);


$ / code $ / pre
$ b $输出:


位置#43:(1; 11)

地址#1:英国伦敦

雇主#11:ST先生



Good day all. I am having a bit of trouble understanding this. I have a JSON that looks like this:

  {
      "data": [
        {
          "id": "43",
          "type": "position",
          "attributes": {
            "address-id": "1",
            "employer-id": "11"
          }
        }
      ],
      "included": [
        {
          "id": "1",
          "type": "address",
          "attributes": {
            "line-1": "21 london london",
            "line-2": "",
            "line-3": "",
            "locality": "",
            "region": "London",
            "post-code": "",
            "country": "UK",
            "latitude": "",
            "longitude": ""
          }
        },
        {
          "id": "11",
          "type": "employer",
          "attributes": {
            "title": "Mr",
            "first-name": "S",
            "last-name": "T"
          }
        }
      ]
    }

And my Retrofit call is:

 @GET("/api/positions")
 Single<PositionResponse> getPosition();

And my PositionResponse class:

public class PositionResponse {

        @SerializedName("data")
        @Expose
        private List<DataResponse> data;
        @SerializedName("included")
        @Expose
        private List<IncludedModel> included;

        public List<DataResponse> getData() {
            return data;
        }

        public void setData(List<DataResponse> data) {
            this.data = data;
        }

        public List<IncludedModel> getIncluded() {
            return included;
        }

        public void setIncluded(List<IncludedModel> included) {
            this.included = included;
        }

        }
    }

Now imagine it has a lot more data. How can I create a custom TypeAdapter or JsonDeserializer for parsing the List<IncludedModel>? For some reason, I can create a custom JsonDeserializer or TypeAdapter for Object, but when it comes to a List, I don't seem to be able to get that to work.

My TypeAdapter is as follows:

  public class IncludedTypeAdapter extends TypeAdapter<ArrayList<IncludedModel>> {

        @Override
        public void write(JsonWriter out, ArrayList<IncludedModel> value) throws IOException {

        }

        @Override
        public ArrayList<IncludedModel> read(JsonReader in) throws IOException {
            ArrayList<IncludedModel> list = new ArrayList<>();
            IncludedModel model = new IncludedModel();
            Gson gson = new Gson();
            in.beginArray();
            String id = null;
            //in.beginObject();
            while(in.hasNext()){
                JsonToken nextToken = in.peek();

                if(JsonToken.BEGIN_OBJECT.equals(nextToken)){
                    in.beginObject();

                } else if(JsonToken.NAME.equals(nextToken)){

                    if(JsonToken.NAME.name().equals("id")){
                        id = in.nextString();
                        model.setId(id);

                    } else if(JsonToken.NAME.name().equals("type")){
                        String type = in.nextString();
                        model.setMytype(type);

                        switch (type) {
                            case BaseModelType.Employer:
                                EmployerResponse employer = gson.fromJson(in, EmployerResponse.class);
                                model.setEmployer(employer);
                                break;
                        }
                    }
                }
            }
            list.add(model);

            return list;
        }

And i register to my Gson:

  GsonBuilder gsonBuilder = new GsonBuilder();
      gsonBuilder.registerTypeAdapter(IncludeModel.class, new IncludedTypeAdapter());
     //gsonBuilder.registerTypeAdapter(new IncludedTypeAdapter());
      gsonBuilder.serializeNulls();
      Gson gson = gsonBuilder.create();

      return gson;

Which I register on retrofit through GsonConverterFactory.

I am getting:

Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 6292 path $.included[0]

which I suspect is because my Retrofit response is <PositionResponse> which is a JsonObject.

To summarize my question: how do I deserialize the List<IncludeModel> object with my own custom type adapter bearing in mind the response type from my Retrofit service is PositionResponse? Many thanks for your patients and answers.

解决方案

It's easy if you're using JSON tree models using JsonDeserializer. Pure type adapters are somewhat an overkill (as well as RuntimeTypeAdapterFactory is, I think, since it's still tree-oriented), and in the most simple case for your JSON document you could use something like this (you can find a similar approach in my yesterday answer having some more explanations, but you case slightly differs).

I'm assuming you would like to have mappings like these:

abstract class Element {

    final String id = null;

    private Element() {
    }

    static final class Address
            extends Element {

        @SerializedName("line-1") final String line1 = null;
        @SerializedName("line-2") final String line2 = null;
        @SerializedName("line-3") final String line3 = null;
        @SerializedName("locality") final String locality = null;
        @SerializedName("region") final String region = null;
        @SerializedName("post-code") final String postCode = null;
        @SerializedName("country") final String country = null;
        @SerializedName("latitude") final String latitude = null;
        @SerializedName("longitude") final String longitude = null;

        private Address() {
        }

        @Override
        public String toString() {
            return country + " " + region;
        }

    }

    static final class Employer
            extends Element {

        @SerializedName("title") final String title = null;
        @SerializedName("first-name") final String firstName = null;
        @SerializedName("last-name") final String lastName = null;

        private Employer() {
        }

        @Override
        public String toString() {
            return title + ' ' + firstName + ' ' + lastName;
        }

    }

    static final class Position
            extends Element {

        @SerializedName("address-id") final String addressId = null;
        @SerializedName("employer-id") final String employerId = null;

        private Position() {
        }

        @Override
        public String toString() {
            return '(' + addressId + ';' + employerId + ')';
        }

    }

}

All you have to do is just:

  • determine the expected object type;
  • "align" the JSON tree (if it fits your needs sure);
  • just delegate the deserialization work to Gson via the deserialization context (your example does not do it well: you're instantiating Gson once again losing the original configuration; you redo all Gson can do out of box: lists and POJO by reflection; JsonToken are much better if checked via switch (by the way enums are singletons and it's perfectly legal to compare them using reference equality ==), etc).

So, it can be implemented by something like this:

final class ElementJsonDeserializer
        implements JsonDeserializer<Element> {

    private static final JsonDeserializer<Element> elementJsonDeserializer = new ElementJsonDeserializer();

    private ElementJsonDeserializer() {
    }

    static JsonDeserializer<Element> getElementJsonDeserializer() {
        return elementJsonDeserializer;
    }

    @Override
    public Element deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
            throws JsonParseException {
        final JsonObject jsonObject = jsonElement.getAsJsonObject();
        final String typeCode = jsonObject.getAsJsonPrimitive("type").getAsString();
        final Class<? extends Element> clazz;
        switch ( typeCode ) {
        case "address":
            clazz = Element.Address.class;
            break;
        case "employer":
            clazz = Element.Employer.class;
            break;
        case "position":
            clazz = Element.Position.class;
            break;
        default:
            throw new JsonParseException("Unrecognized type: " + typeCode);
        }
        reattach(jsonObject, "attributes");
        return context.deserialize(jsonElement, clazz);
    }

    private static void reattach(final JsonObject parent, final String property) {
        final JsonObject child = parent.getAsJsonObject(property);
        parent.remove(property); // remove after we're sure it's a JSON object
        copyTo(parent, child);
    }

    private static void copyTo(final JsonObject to, final JsonObject from) {
        for ( final Entry<String, JsonElement> e : from.entrySet() ) {
            to.add(e.getKey(), e.getValue());
        }
    }

}

Of course, you can refactor the above to extract a strategy to implement the strategy design pattern to reuse it. Put it all together:

final class Response {

    final List<Element> data = null;
    final List<Element> included = null;

}

(The above one looks like a Map<String, List<Element>> but you decide).

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapter(Element.class, getElementJsonDeserializer())
        .create();

public static void main(final String... args)
        throws IOException {
    try ( final JsonReader jsonReader = getPackageResourceJsonReader(Q43811168.class, "data.json") ) {
        final Response response = gson.fromJson(jsonReader, Response.class);
        dump(response.data);
        dump(response.included);
    }
}

private static void dump(final Iterable<Element> elements) {
    for ( final Element e : elements ) {
        System.out.print(e.getClass().getSimpleName());
        System.out.print(" #");
        System.out.print(e.id);
        System.out.print(": ");
        System.out.println(e);
    }
}

Output:

Position #43: (1;11)
Address #1: UK London
Employer #11: Mr S T

这篇关于如何使用定制的Gson解串器分析嵌套的JSON数组对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆