简体   繁体   English

如何使用Java Gson库转换动态JSON响应

[英]How to convert dynamic JSON reponse with Java Gson library

I have an API that can return JSON arrays or objects. 我有一个可以返回JSON数组或对象的API。 Example JSON object JSON对象示例

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

JSON array: JSON数组:

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

When mapping a JSON object response to a POJO I use: 将JSON对象响应映射到POJO时,我使用:

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

When mapping a JSON array response to an array of POJOs I use: 当将JSON数组响应映射到POJO数组时,我使用:

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. 注意:我无法修改服务器响应,这是一个公共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 LinkedTreeMap s. 它返回LinkedTreeMap的列表。 How can I modify the code to return the same content as Object[] ? 如何修改代码以返回与Object[]相同的内容?

Just parse it into JsonElement and check actual element type: 只需将其解析为JsonElement并检查实际的元素类型:

Gson g = new Gson();
JsonParser parser = new JsonParser();
JsonElement e = parser.parse( new StringReader(jsonString) );
if(e instanceof JsonObject) {
    MyEntity myEntity = g.fromJson(e, MyEntity.class);
} else {
    MyEntity[] myEntity =  g.fromJson(e, MyEntity[].class);
}

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. 这取决于如何在此处解释“适当的类型”,因为一旦您每次需要处理从JSON解析的对象时,它将导致instanceof或访客模式获得适当的类型。 If you can't change the API, you can smooth the way you use it. 如果您无法更改API,则可以简化其使用方式。 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). 甚至单个对象也可以仅作为一个元素处理为列表(许多库仅使用具有以下事实的序列/列表进行处理:Java中的Stream API,.NET中的LINQ,JavaScript中的jQuery等)。

Suppose you have the following MyEntity class to handle the elements obtained from the API you need: 假设您具有以下MyEntity类来处理从所需的API获得的元素:

// 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: 接下来,让我们创建一个类型适配器,该适配器将始终将“ true”列表和单个对象对齐,就好像它是列表一样:

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. Gson TypeAdapter设计为以流方式工作,因此从效率角度来看它们很便宜,但实现起来并不那么容易。 The write() method above is implemented just for the sake of not putting throw new UnsupportedOperationException(); 实现上述write()方法只是为了不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). 在那里(我假设您只阅读了该API,但不知道该API是否会消耗“元素或列表”修改请求)。 Now it's necessary to create a type adapter factory to let Gson pick up the right type adapter for every particular type: 现在,必须创建一个类型适配器工厂,以让Gson为每种特定类型选择正确的类型适配器:

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=[]
-----------------------------------

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM