如何将动态JSON响应与Java Gson库进行转换 [英] How to convert dynamic JSON reponse with Java Gson library

查看:82
本文介绍了如何将动态JSON响应与Java Gson库进行转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个可以返回JSON数组或对象的API。示例JSON对象

  {
id:1,
name:name

JSON数组:

 [
{
id:1,
name:name
},
{
id:1,
name:name
}
]

在将JSON对象响应映射到POJO时,我使用:

  MyEntity myEntity = new Gson()。fromJson(jsonString,MyEntity.class); 

将JSON数组响应映射到一个POJO数组时,我使用:

  MyEntity [] myEntity = new GSON()。fromJson(jsonString,MyEntity []。class); 

如何将这两个响应动态地转换为适当的类型?



注意:我无法修改服务器响应,这是一个公共API。



谢谢!



编辑:



我试图实现一个自动执行此操作的方法,但我缺少一些东西。方法

  public< T> T convertResponseToEntity(Class< T> classOfT)
{
JsonElement jsonElement = this.gson.fromJson(getResponseAsString(),JsonElement.class);

if(jsonElement.isJsonArray()){
Type listType = new TypeToken< T>(){}。getType();
返回this.gson.fromJson(getResponseAsString(),listType);
}

return this.gson.fromJson(getResponseAsString(),(Type)classOfT);
}

它返回一个 LinkedTreeMap 秒。我该如何修改代码以返回与 Object [] ?相同的内容?

解决方案


如何将这两个响应动态地转换为适当的类型?

它取决于如何在这里解释适当的类型,因为它会导致 instanceof 或访问者模式在您每次尝试处理从JSON对象解析的对象时获取适当的类型你需要它。如果您无法更改API,则可以平滑使用它的方式。其中一种可能的选择是处理这种响应,就好像一切都是列表。即使只有一个对象可以作为仅包含一个元素的列表进行处理(许多库仅适用于具有以下事实的序列/列表:Java中的Stream API,.NET中的LINQ,JavaScript中的jQuery等)。



假设您有以下 MyEntity 类来处理从您需要的API获取的元素:

//为了测试的目的,package-visible final字段是完美的
// Gson也可以处理final字段
final class MyEntity {

final int id = Integer.valueOf(0); //因为它是原始的,所以不让javac内联0 $ b $ final String name = null;

@Override
public String toString(){
return id +=> +名称;
}

}

接下来,我们创建一个类型适配器它总是将true列表和单个对象对齐,就像它是一个列表一样:
$ b

  final类AlwaysListTypeAdapter< T> 
扩展TypeAdapter< List< T>> {

private final TypeAdapter< T> elementTypeAdapter;

private AlwaysListTypeAdapter(final TypeAdapter< T> elementTypeAdapter){
this.elementTypeAdapter = elementTypeAdapter;
}

static< T> TypeAdapter<列表与LT; T>> getAlwaysListTypeAdapter(final TypeAdapter< T> elementTypeAdapter){
return new AlwaysListTypeAdapter<>(elementTypeAdapter);

$ b @Override
@SuppressWarnings(resource)
public void write(final JsonWriter out,final List< T> list)
throws IOException {
if(list == null){
out.nullValue();
} else {
switch(list.size()){
case 0:
out.beginArray();
out.endArray();
休息;
案例1:
elementTypeAdapter.write(out,list.iterator()。next());
休息;
默认值:
out.beginArray();
for(final T element:list){
elementTypeAdapter.write(out,element);
}
out.endArray();
休息;
}
}
}

@覆盖
public List< T> read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch(token){
case BEGIN_ARRAY:
final List< T> list = new ArrayList<>();
in.beginArray(); (in.peek()!= END_ARRAY){
list.add(elementTypeAdapter.read(in));
}
in.endArray();
return unmodifiableList(list);
case BEGIN_OBJECT:
return singletonList(elementTypeAdapter.read(in));
case NULL:
return null;
案例EN​​D_ARRAY:
案例EN​​D_OBJECT:
案例名称:
案例STRING:
案例NUMBER:
案例BOOLEAN:
案例EN​​D_DOCUMENT:
抛出新的MalformedJsonException(Unexpected token:+ token);
默认值:
//警卫案例:如果Gson有一天会添加另一个令牌?
抛出新的AssertionError(永远不会发生:+令牌);



$ b $ / code $ / pre
$ b $ Gson TypeAdapter 被设计为以流式方式工作,因此从效率的角度来看它们很便宜,但实现起来并不那么容易。上面的 write()方法只是为了不放置 throw new UnsupportedOperationException(); there(I假设你只能读取该API,但不知道该API是否可能使用元素或列表修改请求)。现在有必要创建一个类型适配器工厂,让Gson为每种特定类型选择正确的类型适配器:

  final class AlwaysListTypeAdapterFactory 
实现TypeAdapterFactory {

private static final TypeAdapterFactory alwaysListTypeAdapterFactory = new AlwaysListTypeAdapterFactory();

private AlwaysListTypeAdapterFactory(){
}

static TypeAdapterFactory getAlwaysListTypeAdapterFactory(){
return alwaysListTypeAdapterFactory;
}

@Override
public< T> TypeAdapter< T> create(final Gson gson,final TypeToken< T> typeToken)
throws IllegalArgumentException {
if(List.class.isAssignableFrom(typeToken.getRawType())){
final类型elementType = getElementType( typeToken);
// Class< T>实例可以与==
进行比较final TypeAdapter<?> elementTypeAdapter = elementType == MyEntity.class? gson.getAdapter(MyEntity.class):null;
//找到支持的元素类型适配器?
if(elementTypeAdapter!= null){
@SuppressWarnings(unchecked)
final TypeAdapter< T> castTypeAdapter =(TypeAdapter< T>)getAlwaysListTypeAdapter(elementTypeAdapter);
返回castTypeAdapter;
}
}
//不是可以处理的类型?让Gson选择一个更合适的本身
return null;
}

//尝试检测列表元素类型
private static Type getElementType(final TypeToken<?> typeToken){
final Type listType = typeToken。的getType();
return listType instanceof ParameterizedType
? ((ParameterizedType)listType).getActualTypeArguments()[0]
:Object.class;
}

}

毕竟它是如何使用的:

  private static final Type responseItemListType = new TypeToken< List< MyEntity>>(){
} .getType();

private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(getAlwaysListTypeAdapterFactory())
.create();

public static void main(final String ... args){
test();
test({\id \:1,\name \:\name \});
test([{\id \:1,\name \:\name\},{\id \:1,\ name\ :\ name\ }]);
test([]);
}

private static void test(final String incomingJson){
final List< MyEntity> list = gson.fromJson(incomingJson,responseItemListType);
System.out.print(LIST =);
System.out.println(list);
System.out.print(JSON =);
gson.toJson(list,responseItemListType,System.out); //不需要创建一个中间字符串,只需要流
System.out.println();
System.out.println(-----------------------------------);





输出:

  LIST = null 
JSON = null
------------------------- ----------
LIST = [1 => name]
JSON = {id:1,name:name}
- ---------------------------------
LIST = [1 => name,1 =>名称]
JSON = [{id:1,name:name},{id:1,name:name}]
----- ------------------------------
LIST = []
JSON = []
-----------------------------------


I have an API that can return JSON arrays or objects. Example JSON object

{
  "id": 1,
  "name": "name"
}

JSON array:

[
   {
       "id": 1,
       "name": "name"
   },
   {
       "id": 1,
       "name": "name"
   }
]

When mapping a JSON object response to a POJO I use:

MyEntity myEntity = new Gson().fromJson(jsonString, MyEntity.class);

When mapping a JSON array response to an array of POJOs I use:

MyEntity[] myEntity = new GSON().fromJson(jsonString, MyEntity[].class);

How can I convert those two responses to the appropriate types dynamically?

NOTE: I can't modify the server response, this is a public API.

Thank you!

EDIT:

I am trying to implement a method that does this automatically but I am missing something. The method

public <T> T convertResponseToEntity(Class<T> classOfT)
{
    JsonElement jsonElement =  this.gson.fromJson(getResponseAsString(), JsonElement.class);

    if (jsonElement.isJsonArray()) {
       Type listType = new TypeToken<T>(){}.getType();
       return this.gson.fromJson(getResponseAsString(), listType);
    }

    return this.gson.fromJson(getResponseAsString(), (Type) classOfT);
}

It returns a list of LinkedTreeMaps. How can I modify the code to return the same content as Object[]?

解决方案

How can I convert those 2 responses dynamically to the appropriate type?

It depends on how to interpret the "appropriate type" here because it would lead to instanceof or visitor pattern to get the appropriate type once you try to handle the parsed-from-JSON object every time you need it. If you can't change the API, you can smooth the way you use it. One of possible options here is handling such response as if everything is a list. Even a single object can be handled as a list with one element only (and many libraries work with sequences/lists only having that fact: Stream API in Java, LINQ in .NET, jQuery in JavaScript, etc).

Suppose you have the following MyEntity class to handle the elements obtained from the API you need:

// For the testing purposes, package-visible final fields are perfect
// Gson can deal with final fields too
final class MyEntity {

    final int id = Integer.valueOf(0); // not letting javac to inline 0 since it's primitive
    final String name = null;

    @Override
    public String toString() {
        return id + "=>" + name;
    }

}

Next, let's create a type adapter that will always align "true" lists and single objects as if it were a list:

final class AlwaysListTypeAdapter<T>
        extends TypeAdapter<List<T>> {

    private final TypeAdapter<T> elementTypeAdapter;

    private AlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) {
        this.elementTypeAdapter = elementTypeAdapter;
    }

    static <T> TypeAdapter<List<T>> getAlwaysListTypeAdapter(final TypeAdapter<T> elementTypeAdapter) {
        return new AlwaysListTypeAdapter<>(elementTypeAdapter);
    }

    @Override
    @SuppressWarnings("resource")
    public void write(final JsonWriter out, final List<T> list)
            throws IOException {
        if ( list == null ) {
            out.nullValue();
        } else {
            switch ( list.size() ) {
            case 0:
                out.beginArray();
                out.endArray();
                break;
            case 1:
                elementTypeAdapter.write(out, list.iterator().next());
                break;
            default:
                out.beginArray();
                for ( final T element : list ) {
                    elementTypeAdapter.write(out, element);
                }
                out.endArray();
                break;
            }
        }
    }

    @Override
    public List<T> read(final JsonReader in)
            throws IOException {
        final JsonToken token = in.peek();
        switch ( token ) {
        case BEGIN_ARRAY:
            final List<T> list = new ArrayList<>();
            in.beginArray();
            while ( in.peek() != END_ARRAY ) {
                list.add(elementTypeAdapter.read(in));
            }
            in.endArray();
            return unmodifiableList(list);
        case BEGIN_OBJECT:
            return singletonList(elementTypeAdapter.read(in));
        case NULL:
            return null;
        case END_ARRAY:
        case END_OBJECT:
        case NAME:
        case STRING:
        case NUMBER:
        case BOOLEAN:
        case END_DOCUMENT:
            throw new MalformedJsonException("Unexpected token: " + token);
        default:
            // A guard case: what if Gson would add another token someday?
            throw new AssertionError("Must never happen: " + token);
        }
    }

}

Gson TypeAdapter are designed to work in streaming fashion thus they are cheap from the efficiency perspective, but not that easy in implementation. The write() method above is implemented just for the sake of not putting throw new UnsupportedOperationException(); there (I'm assuming you only read that API, but don't know if that API might consume "either element or a list" modification requests). Now it's necessary to create a type adapter factory to let Gson pick up the right type adapter for every particular type:

final class AlwaysListTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory alwaysListTypeAdapterFactory = new AlwaysListTypeAdapterFactory();

    private AlwaysListTypeAdapterFactory() {
    }

    static TypeAdapterFactory getAlwaysListTypeAdapterFactory() {
        return alwaysListTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken)
            throws IllegalArgumentException {
        if ( List.class.isAssignableFrom(typeToken.getRawType()) ) {
            final Type elementType = getElementType(typeToken);
            // Class<T> instances can be compared with ==
            final TypeAdapter<?> elementTypeAdapter = elementType == MyEntity.class ? gson.getAdapter(MyEntity.class) : null;
            // Found supported element type adapter?
            if ( elementTypeAdapter != null ) {
                @SuppressWarnings("unchecked")
                final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) getAlwaysListTypeAdapter(elementTypeAdapter);
                return castTypeAdapter;
            }
        }
        // Not a type that can be handled? Let Gson pick a more appropriate one itself
        return null;
    }

    // Attempt to detect the list element type  
    private static Type getElementType(final TypeToken<?> typeToken) {
        final Type listType = typeToken.getType();
        return listType instanceof ParameterizedType
                ? ((ParameterizedType) listType).getActualTypeArguments()[0]
                : Object.class;
    }

}

And how it's used after all:

private static final Type responseItemListType = new TypeToken<List<MyEntity>>() {
}.getType();

private static final Gson gson = new GsonBuilder()
        .registerTypeAdapterFactory(getAlwaysListTypeAdapterFactory())
        .create();

public static void main(final String... args) {
    test("");
    test("{\"id\":1,\"name\":\"name\"}");
    test("[{\"id\":1,\"name\":\"name\"},{\"id\":1,\"name\":\"name\"}]");
    test("[]");
}

private static void test(final String incomingJson) {
    final List<MyEntity> list = gson.fromJson(incomingJson, responseItemListType);
    System.out.print("LIST=");
    System.out.println(list);
    System.out.print("JSON=");
    gson.toJson(list, responseItemListType, System.out); // no need to create an intermediate string, let it just stream
    System.out.println();
    System.out.println("-----------------------------------");
}

The output:

LIST=null
JSON=null
-----------------------------------
LIST=[1=>name]
JSON={"id":1,"name":"name"}
-----------------------------------
LIST=[1=>name, 1=>name]
JSON=[{"id":1,"name":"name"},{"id":1,"name":"name"}]
-----------------------------------
LIST=[]
JSON=[]
-----------------------------------

这篇关于如何将动态JSON响应与Java Gson库进行转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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