解析包含多个嵌套对象的 JSON 对象而不为每个嵌套对象创建类

3

我正在Android中使用GSON来解析JSON对象,其中一部分包含多个嵌套对象,这些对象都持有相同的字段。例如,JSON结构类似于:

        {
        "name": "nestedJSONExample",   
        "divisions": {
                "division1": {
                  "id": string
                  "name": string,
                  "alsoKnownAs": [
                    string
                  ],
                }
                "division2": {
                  "id": string
                  "name": string,
                  "alsoKnownAs": [
                    string
                  ],
                }

                 ...

                "division99" {
                  "id": string
                  "name": string,
                  "alsoKnownAs": [
                    string
                  ],
               }
            }
         }  

在这个例子中,“division##”嵌套对象中包含相同的字段,有没有一种方法可以将此JSON解析为Java类,而无需为每个“division##”对象创建模型类?
例如,我是否可以创建一个类似于以下结构的Java结构:
divisions.division##.id

不需要为每个单独的部门创建类吗?

您可以将整个JSON文档解析为JsonElement,然后遍历它。 - Lyubomyr Shaydariv
2个回答

3
您似乎有些困惑:针对每个division##节点,您不需要一个映射类,因为您可以多次重复使用一个类,而不管属性名称如何。您可能需要零到两个自定义映射类,具体取决于您的喜好方式:
  • 如果自己遍历解析后的JSON对象,则不需要自定义映射类;
  • 如果应用高级解析技术并将映射与类型适配器或JSON对象结合使用,则需要1个自定义映射类;
  • 精确映射需要2个自定义映射类。
下面的示例是使用Java 8语言特性和Java 8 Stream API编写的,但可以轻松地使用Java 6重新编写。下面的JSON常量只是一个包含以下JSON文档的String
{
    "name": "nestedJSONExample",
    "divisions": {
        "division1": {"id": "id1", "name": "name1", "alsoKnownAs": ["alsoKnownAs1A"]},
        "division2": {"id": "id2", "name": "name2", "alsoKnownAs": ["alsoKnownAs2A"]},
        "division3": {"id": "id3", "name": "name3", "alsoKnownAs": ["alsoKnownAs3A"]},
        "division4": {"id": "id4", "name": "name4", "alsoKnownAs": ["alsoKnownAs4A"]},
        "division5": {"id": "id5", "name": "name5", "alsoKnownAs": ["alsoKnownAs5A"]},
        "division6": {"id": "id6", "name": "name6", "alsoKnownAs": ["alsoKnownAs6A"]}
    }
}

无映射

JsonElement 是一个内置的Gson类,表示任何JSON元素。通过结合JsonElement类及其子类元素,Gson可以构建反映给定JSON文档结构的JSON树。因此,只需从根部遍历即可。

final Gson gson = new Gson();
final List<String> ids = gson.fromJson(JSON, JsonElement.class)
        .getAsJsonObject()
        .get("divisions") // get the divisions property
        .getAsJsonObject()
        .entrySet() // and traverse its key/value pairs
        .stream()
        .map(Entry::getValue) // discarding the keys
        .map(JsonElement::getAsJsonObject)
        .map(jo -> jo.get("id")) // take the id property from the every `division` object
        .map(JsonElement::getAsJsonPrimitive)
        .map(JsonPrimitive::getAsString)
        .collect(toList());
System.out.println(ids);

精确映射

在这里,您只需要使用两个映射类来描述JSON对象之间的关系。 divisions节点可以仅是一个包含任意键和Division值的Map

final class OuterWithMap {
    //String name;
    Map<String, Division> divisions;
}

final class Division {
    String id;
    //String name;
    //List<String> alsoKnownAs;
}

final Gson gson = new Gson();
final List<String> ids = gson.fromJson(JSON, OuterWithMap.class)
        .divisions
        .values() // use map values only ignoring the keys
        .stream()
        .map(d -> d.id)
        .collect(toList());
System.out.println(ids);

不精确的映射

这是最复杂的一个,展示了使用Gson解析JSON和将给定的JSON文档映射到映射类中的高级技术。由于映射可能无法反映真实结构,因此需要在运行时进行转换。

final class OuterWithList {
    //String name;
    @JsonAdapter(NoKeysTypeAdapterFactory.class)
    List<Division> divisions;
}

final class NoKeysTypeAdapterFactory
        implements TypeAdapterFactory {

    // No accessible constructor needed - Gson can instantiate it itself
    private NoKeysTypeAdapterFactory() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // Is it a list?
        if ( List.class.isAssignableFrom(typeToken.getRawType()) ) {
            // Try to determine the list element type
            final Type elementType = getElementType(typeToken.getType());
            // And create a custom type adapter instance bound to the specific list type
            @SuppressWarnings("unchecked")
            final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) getNoKeysTypeAdapter(gson, elementType);
            return typeAdapter;
        }
        // Otherwise just tell Gson try to find another appropriate parser
        return null;
    }

    private static Type getElementType(final Type type) {
        // Is it a generic type with type parameters?
        if ( type instanceof ParameterizedType ) {
            final ParameterizedType parameterizedType = (ParameterizedType) type;
            // If yes, then just take the first type argument since java.util.List can only one type
            return parameterizedType.getActualTypeArguments()[0];
        }
        // Otherwise java.lang.Object due to either Java generics type erasure or raw types usage
        return Object.class;
    }

}

final class NoKeysTypeAdapter<E>
        extends TypeAdapter<List<E>> {

    private final Gson gson;
    private final Type elementType;

    private NoKeysTypeAdapter(final Gson gson, final Type elementType) {
        this.gson = gson;
        this.elementType = elementType;
    }

    static <E> TypeAdapter<List<E>> getNoKeysTypeAdapter(final Gson gson, final Type elementType) {
        return new NoKeysTypeAdapter<>(gson, elementType);
    }

    @Override
    public void write(final JsonWriter out, final List<E> value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<E> read(final JsonReader in)
            throws IOException {
        final List<E> list = new ArrayList<>();
        // Make sure that the next JSON stream token is `{`
        in.beginObject();
        // Read until the object ends
        while ( in.peek() != END_OBJECT ) {
            // Ignore the found JSON object property name
            in.nextName();
            // And delegate the property value parsing to a downstream parser
            final E element = gson.fromJson(in, elementType);
            list.add(element);
        }
        // Make sure that the JSON stream is finished with the `}` token
        in.endObject();
        return list;
    }

}

使用特殊的查询库

有一些类似于JsonPath的库可以使查询JSON文档变得更加容易。 JsonPath可以在没有Gson的情况下工作,但据我所知,它使用另一个JSON解析库,不会自己解析JSON(但实际上我不知道具体情况)。以下是使用的例子:

final JsonPath jsonPath = JsonPath.compile("$.divisions.*.id");
final List<String> ids = jsonPath.<JSONArray>read(JSON)
        .stream()
        .map(o -> (String) o)
        .collect(toList());
System.out.println(ids);

所有以上四个示例的输出都是:
[id1,id2,id3,id4,id5,id6]

非常感谢您提供如此详细的答案。使用我现有的映射,我能够实现您的“精确映射”示例来解析JSON输出。在我的当前代码中,我使用StringRequest获取JSON字符串,并使用您的示例将其解析为Java对象。 - C. Marr

1
使用GSON,你最好的选择是编写一个自定义的反序列化器(示例)或者一个TypeAdapter(示例),这将允许你对结构进行任何操作,然后返回一个单一的(顶层)对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接