Перейти к основному содержимому

Преобразование JSON в карту с помощью Gson

· 4 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

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 , а объекты — в LinkedTreeMap .

Однако при наличии повторяющихся ключей приведение не удастся и будет выдано исключение 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());

Теперь, если мы создадим наш тип Map как Map<String, Object> , тогда синтаксический анализатор по-прежнему будет использовать значение по умолчанию, как мы видели в предыдущем разделе.

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

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

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

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

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

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

Теперь нам нужно зарегистрировать его в GsonBuilder для нашего целевого типа Map<String, Date > и создать настраиваемый объект Gson .

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

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 .