«1. Введение

В этом кратком руководстве мы узнаем, как преобразовать строку JSON в карту с помощью Gson от Google.

Мы рассмотрим три разных подхода к достижению этой цели и обсудим их плюсы и минусы — с некоторыми практическими примерами.

2. Передача Map.class

В общем, Gson предоставляет следующий API в своем классе Gson для преобразования строки JSON в объект:

public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException;

Из подписи очень ясно, что второй параметр — это класс объекта, который мы собираемся анализировать в JSON. В нашем случае это должен быть Map.class:

String jsonString = "{'employee.name':'Bob','employee.salary':10000}";
Gson gson = new Gson();
Map map = gson.fromJson(jsonString, Map.class);
Assert.assertEquals(2, map.size());
Assert.assertEquals(Double.class, map.get("employee.salary").getClass());

Этот подход позволит наилучшим образом угадать тип значения для каждого свойства.

Например, числа будут преобразованы в Double, true и false — в Boolean, а объекты — в LinkedTreeMaps.

Однако при наличии повторяющихся ключей приведение не удастся и будет выдано исключение JsonSyntaxException.

И из-за стирания типов мы также не сможем настроить это поведение приведения. Итак, если нам нужно указать типы ключа или значения, нам понадобится другой подход.

3. Использование TypeToken

Чтобы решить проблему стирания типов для универсальных типов, Gson имеет перегруженную версию API:

public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException;

Мы можем создать карту с параметрами типа, используя TypeToken Gson. . Класс TypeToken возвращает экземпляр ParameterizedTypeImpl, который сохраняет тип ключа и значения даже во время выполнения. мы видели в предыдущем разделе.

String jsonString = "{'Bob' : {'name': 'Bob Willis'},"
  + "'Jenny' : {'name': 'Jenny McCarthy'}, "
  + "'Steve' : {'name': 'Steven Waugh'}}";
Gson gson = new Gson();
Type empMapType = new TypeToken<Map<String, Employee>>() {}.getType();
Map<String, Employee> nameEmployeeMap = gson.fromJson(jsonString, empMapType);
Assert.assertEquals(3, nameEmployeeMap.size());
Assert.assertEquals(Employee.class, nameEmployeeMap.get("Bob").getClass());

Конечно, это все еще возвращается к Gson для принуждения примитивных типов. Однако их тоже можно настроить.

4. Использование пользовательского десериализатора JsonDeserializer

Когда нам нужен детальный контроль над построением нашего объекта Map, мы можем реализовать собственный десериализатор типа JsonDeserializer\u003cMap\u003e.

Чтобы увидеть пример, предположим, что наш JSON содержит имя сотрудника в качестве ключа и дату его приема на работу в качестве значения. Кроме того, предположим, что формат даты — гггг/мм/дд, что не является стандартным форматом для Gson.

Мы можем настроить Gson для другого анализа нашей карты, а затем реализовать JsonDeserializer:

Теперь мы должны зарегистрировать его в GsonBuilder для нашего целевого типа Map\u003cString, Date\u003e и создать настроенный Объект Гсона.

public class StringDateMapDeserializer implements JsonDeserializer<Map<String, Date>> {

    private SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");

    @Override
    public Map<String, Date> deserialize(JsonElement elem,
          Type type,
          JsonDeserializationContext jsonDeserializationContext) {
        return elem.getAsJsonObject()
          .entrySet()
          .stream()
          .filter(e -> e.getValue().isJsonPrimitive())
          .filter(e -> e.getValue().getAsJsonPrimitive().isString())
          .collect(
            Collectors.toMap(
              Map.Entry::getKey,
              e -> formatDate(e.getValue())));
    }

    private Date formatDate(Object value) {
        try {
            return format(value.getAsString());
        } catch (ParseException ex) {
            throw new JsonParseException(ex);
        }
    }
}

Когда мы вызываем API fromJson для этого объекта Gson, синтаксический анализатор вызывает пользовательский десериализатор и возвращает нужный экземпляр карты:

Эта тактика также полезна, когда наша карта может содержать разнородные значения и у нас есть справедливая представление о том, сколько различных типов значений может быть.

String jsonString = "{'Bob': '2017-06-01', 'Jennie':'2015-01-03'}";
Type type = new TypeToken<Map<String, Date>>(){}.getType();
Gson gson = new GsonBuilder()
  .registerTypeAdapter(type, new StringDateMapDeserializer())
  .create();
Map<String, Date> empJoiningDateMap = gson.fromJson(jsonString, type);
Assert.assertEquals(2, empJoiningDateMap.size());
Assert.assertEquals(Date.class, empJoiningDateMap.get("Bob").getClass());

Чтобы узнать больше о пользовательском десериализаторе в Gson, прочтите Поваренную книгу по десериализации Gson.

5. Заключение

В этой короткой статье мы узнали о нескольких способах построения карты из строки в формате JSON. И мы также обсудили правильные варианты использования этих вариантов.

Исходный код примеров доступен на GitHub.

«