简体   繁体   中英

Parse JSON object containing multiple nested objects without making class for each nested object

I'm using GSON in Android to parse a JSON object, part of which contains multiple nested objects holding all of the same fields. For example, the JSON structure looks similar to this:

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

                 ...

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

In this example all of the "division##" nested objects contain all of the same fields, is there a way to parse this JSON into a Java class without creating model classes for each "division##" object?

ie can I create a Java structure like:

divisions.division##.id

without having to make classes for each individual division?

You seem to have a little confusion: you don't need a mapping class for each division## node since you can reuse one class multiple times regardless the property names. You might need from zero to two custom mapping classes regarding the way you prefer:

  • 0 custom mapping classes if traversing a parsed JSON object on your own;
  • 1 custom mapping class if applying advanced parsing techniques and combining the mapping with type adapters or JSON objects;
  • 2 custom mapping classes for exact mapping.

The examples below are written with Java 8 language features and Java 8 Stream API but can be re-written with Java 6 easily. The JSON constant below is just a String with the following JSON document:

{
    "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"]}
    }
}

No mappings

JsonElement is a built-in Gson class representing any JSON element. Combining JsonElement class and its child classes elements, Gson can build a JSON tree that reflects a given JSON document structure. So just traversing from the root is enough.

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);

Exact mappings

Here you could need just two mapping classes to describe the relations between JSON objects. The divisions node can be just a Map holding arbitrary keys and Division values.

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);

Not exact mappings

This is the most complicated one and shows advanced techniques in parsing JSON with Gson and mapping given JSON documents to mapping classes may not reflect the real structure therefore making transformations on-fly.

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;
    }

}

Using a special querying library

There are some libraries like JsonPath that can make querying JSON documents somewhat easier. JsonPath can work without Gson, however, as far as I understand, it uses another JSON parsing library, and does not parse JSON itself (but I don't know how it actually is). Example of use:

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);

All four examples above have the following output:

[id1, id2, id3, id4, id5, id6]

使用GSON你最好的选择是编写一个自定义反序列化器( 示例 )或一个TypeAdapter( 示例 ),这将允许你对结构做任何你想做的事情然后返回一个(顶层)对象

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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