Пользовательский десериализатор карт Java в Gson

Можно ли преобразовать массив пар объектов в Map, выбирая значения из каждой пары, без создания промежуточного POJO?

Например, предположим, что существует 2 класса Java (Key.class, Value.class) и массив JSON, построенный из сериализованных пар их объектов, как показано ниже.

[
  {"key":{}, "value":{}}, // Objects k1, v1
  {"key":{}, "value":{}}, // Objects k2, v2
  {"key":{}, "value":{}}, // Objects k3, v3
  {"key":{}, "value":{}}, // Objects k4, v4
]

Может ли Gson «напрямую» (т.е. без определения POJO) десериализовать вышеуказанный массив в Map, в котором фактические ключи и значения заполняются в результате манипуляции соответствующими объектами Key и Value, а не «полными» объектами?

Другими словами, учитывая два типа JavaK и V, которые могут отличаться от Key и Value, десериализация для каждой пары должна быть

(k1:Key, v1:Value) --> (k1':K, v1':V)

куда

k1' = function(k1)
v1' = function(v1)

Такой кастомный адаптер не работает:

GsonBuilder builder = new GsonBuilder();
Type type = (new TypeToken<Map<K, V>>() {}).getType();    
builder.registerTypeAdapter(type, new JsonDeserializer<Map<Key, Value>>() {
  @Override
  public Map<K, V> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
    // Custom deserialization block,
    // invoking the `function()` method
  }
Gson gson = builder.create();

Использование вышеуказанного адаптера приводит к тому, что Gson передает весь массив методу deserialize(), где им нужно управлять вручную, что утомительно и чревато ошибками.

С кастомного POJO вроде

public class KeyValue {
  Key k;
  Value v;
}

Вышеупомянутый массив JSON может быть десериализован в массив или набор объектов KeyValue, которые затем могут быть преобразованы в Map<K, V>. Но можно ли сделать то же самое без KeyValue POJO;

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
1 042
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если вы хотите избежать класса POJO, который вы называете KeyValue, я думаю, что единственный способ сделать это с помощью gson - создать адаптер. Причина в том, что ваш JSON не соответствует какому-либо универсальному встроенному классу, и поэтому gson не сможет его понять, не имея для этого настраиваемой логики.

Поскольку у вас есть классы Key и Value, вы можете использовать их в своем собственном адаптере. Я думаю, вы обнаружите, что это снижает утомительность кода.

public static void main(String[] args) throws IOException {
        GsonBuilder builder = new GsonBuilder();
        Type type = (new TypeToken<Map<Key, Value>>() {}).getType();    
        builder.registerTypeAdapter(type, new JsonDeserializer<Map<Key, Value>>() {
          @Override
          public Map<Key, Value> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
              Map<Key, Value> resultMap = new HashMap<>();
              JsonArray jArr = json.getAsJsonArray();
              for(int i=0; i < jArr.size(); i++){
                  JsonObject item = jArr.get(i).getAsJsonObject();
                  processKeyValue(item, resultMap);
              }
              return resultMap;
          }

          private void processKeyValue(JsonObject mapEntry, Map<Key, Value> resultMap){
              Gson gson = new Gson();
              Key key = gson.fromJson(mapEntry.get("key"), Key.class);
              Value value = gson.fromJson(mapEntry.get("value"), Value.class);
              resultMap.put(key, value);
          }
        });
        Gson gson = builder.create();

        //Note: assume that the variable "json" is a JSON string. I removed my code for getting the string since I'm just reading from a file and I don't know how you're getting your data.

        // Deserialization

        Map<Key,Value> outputMap = gson.fromJson(json, type);


        System.out.println("RESULTS: "+outputMap.toString());
    }

Я использовал данные вашего примера, но немного изменил их, чтобы было немного больше контента. Я создал два очень простых класса POJO для Key и Value - у них есть только поле String с именем id, средства получения и установки для этого поля и метод toString.

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

[
  {"key":{"id":"k1"}, "value":{"id":"v1"}}, 
  {"key":{"id":"k2"}, "value":{"id":"v2"}},
  {"key":{"id":"k3"}, "value":{"id":"v3"}}, 
  {"key":{"id":"k4"}, "value":{"id":"v4"}}
]

Результат:

RESULTS: {Key [id=k2]=Value [id=v2], Key [id=k1]=Value [id=v1], Key [id=k3]=Value [id=v3], Key [id=k4]=Value [id=v4]}

Да, похоже, это единственный подход. Спасибо.

PNS 15.09.2018 06:18

Другие вопросы по теме