Search code examples
javajsonarraylistgsonjson-deserialization

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


Given a JSON map like this:

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

I would like to convert it into a Java ArrayList of Categories (not a Map), like this:

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

So I expect data to look like this:

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

I've tried using the Gson library:

app/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. But I get an IllegalStateException if I try to convert the JSON map to a Java List:

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

or

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?


Solution

  • Easiest is to simply parse into Map, then convert Map to 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()));
    

    The alternative is to write your own TypeAdapter, or to use another JSON library.