繁体   English   中英

如何使用 ZA79956B0B2E527BDA219D5DDAA 将 JSON map 转换为自定义 Java 列表

[英]How to convert a JSON map into a custom Java list, using Gson?

给定一个 JSON map 像这样:

{
    "category1": [],
    "category2": ["item1"],
    "category3": ["item1", "item2"]
}

我想将其转换为类别的 Java ArrayList(不是地图),如下所示:

class Category {
  final String categoryName;
  final ArrayList<String> items;

  public Category(String categoryName, ArrayList<String> items) {
    this.categoryName = categoryName;
    this.items = items;
  }
}

ArrayList<Category> data = new ArrayList<>();

所以我希望data看起来像这样:

for (Category c: data) {
  System.out.println(c.categoryName + ", " + c.items);
}
/**
Expected data =
category1, empty list [] or null
category2, [item1]
category2, [item1, item2]
**/

我试过使用Gson库:

应用程序/build.gradle:

dependencies {
   ...
   implementation 'com.google.code.gson:gson:2.8.6'
}

JsonConverterTest.java

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;

import org.junit.Test;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class JsonConverterTest {

  @Test
  public void gsonMapToListTest() {
    String json = "{\"category1\": [],\"category2\": [\"item1\"],\"category3\": [\"item1\", \"item2\"]}";

    class Category {
      final String categoryName;
      final ArrayList<String> items;

      public Category(String categoryName, ArrayList<String> items) {
        this.categoryName = categoryName;
        this.items = items;
      }
    }

    ArrayList<Category> expectedData = new ArrayList<>();
    expectedData.add(new Category("category1", new ArrayList<String>()));
    expectedData.add(new Category("category2", new ArrayList<>(Arrays.asList("item1"))));
    expectedData.add(new Category("category2", new ArrayList<>(Arrays.asList("item1", "item2"))));

    System.out.println("Expected Data:");
    for (Category c: expectedData) {
      System.out.println(c.categoryName + ", " + c.items);
    }

    // This works, but it returns a Map instead of a List
    LinkedTreeMap<String, ArrayList<String>> dataMap = new Gson().fromJson(json, LinkedTreeMap.class);
    System.out.println("\n data as Map = " + dataMap);

    // This causes "IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $"
    Type listOfCategoriesType = new TypeToken<ArrayList<Category>>() {}.getType();
    ArrayList<Category> data = new Gson().fromJson(json, listOfCategoriesType); // IllegalStateException here
    assertThat(data, is(expectedData));

    // This causes "java.lang.IllegalStateException: Not a JSON Array: {...}"
    JsonArray jsonArray = JsonParser.parseString(json).getAsJsonArray(); // IllegalStateException here
    data = new Gson().fromJson(jsonArray, listOfCategoriesType);
    assertThat(data, is(expectedData));
  }
}

Using the guides https://www.baeldung.com/gson-list and https://futurestud.io/tutorials/gson-mapping-of-maps , I can only convert the JSON map to a Java Map. 但是,如果我尝试将 JSON map 转换为 Java 列表,则会收到 IllegalStateException:

Type listOfCategoriesType = new TypeToken<ArrayList<Category>>() {}.getType();
ArrayList<Category> data = new Gson().fromJson(json, listOfCategoriesType); // IllegalStateException

或者

JsonArray jsonArray = JsonParser.parseString(json).getAsJsonArray(); // IllegalStateException
ArrayList<Category> data2 = new Gson().fromJson(jsonArray, listOfCategoriesType);

So what is the correct way in Gson to convert the Json Map to a Java list, as per the unit test gsonMapToListTest() above?

最简单的方法是简单地解析为Map ,然后将Map转换为List<Category>

LinkedTreeMap<String, ArrayList<String>> dataMap = new Gson().fromJson(json,
        new TypeToken<LinkedTreeMap<String, ArrayList<String>>>() {}.getType());

ArrayList<Category> dataList = new ArrayList<>();
for (Entry<String, ArrayList<String>> entry : dataMap.entrySet())
    dataList.add(new Category(entry.getKey(), entry.getValue()));

另一种方法是编写自己的TypeAdapter ,或使用另一个 JSON 库。

不是最简单的方法,但 Gson 允许实现这样的类型适配器,因此您可以反序列化 JSON 结果 object15216DZ0B022EBF8object15216D3D2EBF67AFC16363。

public final class CombinerTypeAdapterFactory<V, C>
        implements TypeAdapterFactory {

    // used to create a collection to populate
    private final TypeToken<? extends Collection<? super C>> collectionTypeToken;
    // represents a type of each map entry value
    private final TypeToken<V> valueTypeToken;
    // a strategy to map an entry to an element
    private final BiFunction<? super String, ? super V, ? extends C> combine;

    private CombinerTypeAdapterFactory(final TypeToken<? extends Collection<? super C>> collectionTypeToken, final TypeToken<V> valueTypeToken,
            final BiFunction<? super String, ? super V, ? extends C> combine) {
        this.collectionTypeToken = collectionTypeToken;
        this.valueTypeToken = valueTypeToken;
        this.combine = combine;
    }

    public static <V, C> TypeAdapterFactory create(final TypeToken<List<C>> collectionTypeToken, final TypeToken<V> valueTypeToken,
            final BiFunction<? super String, ? super V, ? extends C> combine) {
        return new CombinerTypeAdapterFactory<>(collectionTypeToken, valueTypeToken, combine);
    }

    @Override
    @Nullable
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        // check if supported
        if ( !typeToken.equals(collectionTypeToken) ) {
            return null;
        }
        // grab the value type adapter
        final TypeAdapter<V> valueTypeAdapter = gson.getAdapter(valueTypeToken);
        // and the type adapter the collection to be populated
        final TypeAdapter<? extends Collection<? super C>> collectionTypeAdapter = gson.getDelegateAdapter(this, collectionTypeToken);
        return new TypeAdapter<T>() {
            @Override
            public void write(final JsonWriter out, final T value) {
                throw new UnsupportedOperationException(); // TODO
            }

            @Override
            public T read(final JsonReader in)
                    throws IOException {
                // assuming all of those are always { e1, e2, e3, ... }
                in.beginObject();
                // a hack to create an empty collection (it's modifiable right?)
                final Collection<? super C> collection = collectionTypeAdapter.fromJson("[]");
                // for each entry in the map that's being read
                while ( in.hasNext() ) {
                    // get is name
                    final String name = in.nextName();
                    // and its value
                    final V value = valueTypeAdapter.read(in);
                    // combine
                    final C combined = combine.apply(name, value);
                    // and add to the list
                    collection.add(combined);
                }
                in.endObject();
                // we know better than javac
                @SuppressWarnings("unchecked")
                final T result = (T) collection;
                return result;
            }
        }
                .nullSafe();
    }

}

那么下面的测试是绿色的:

public final class CombinerTypeAdapterFactoryTest {

    @AllArgsConstructor
    @EqualsAndHashCode
    @ToString
    private static final class Category {

        @Nullable
        final String categoryName;

        @Nullable
        final List<String> items;

    }

    private static final Gson gson = new GsonBuilder()
            .disableInnerClassSerialization()
            .disableHtmlEscaping()
            .registerTypeAdapterFactory(CombinerTypeAdapterFactory.create(new TypeToken<List<Category>>() {}, new TypeToken<List<String>>() {}, Category::new))
            .create();

    @Test
    public void test()
            throws IOException {
        try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(CombinerTypeAdapterFactoryTest.class.getResourceAsStream("input.json"))) ) {
            final List<Category> actualCategories = gson.fromJson(jsonReader, new TypeToken<List<Category>>() {}.getType());
            Assertions.assertIterableEquals(
                    ImmutableList.of(
                            new Category("category1", ImmutableList.of()),
                            new Category("category2", ImmutableList.of("item1")),
                            new Category("category3", ImmutableList.of("item1", "item2"))
                    ),
                    actualCategories
            );
        }
    }

}

暂无
暂无

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

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