Сериализация Gson подкласса Map, который также имеет явно определенные свойства - PullRequest
1 голос
/ 11 апреля 2019

У меня есть коллекция сгенерированных Java-бинов, где каждый бин определяет одно или несколько полей, плюс его подклассы HashMap<String, T>, где T - параметризованный тип.Сгенерированные поля моделируют явно определенные свойства схемы в JSON schema, а подклассы HashMap выполняются для поддержки дополнительных " произвольных " свойств определенного типа (указанных в схеме JSON "* 1007).* AdditionalProperties"для тех, кто знаком со схемой JSON).Вот пример сгенерированного компонента:

public class MyModel extends HashMap<String, Foo> {
    private String prop1;
    private Long prop2;

    public String getProp1() {
        return prop1;
    }

    public void setProp1(String value) {
        this.prop1 = value;
    }

    public Long getProp2() {
        return prop2;
    }

    public void setProp2(Long prop2) {
        this.prop2 = prop2;
    }
}

В этом примере пользователь может установить prop1 или prop2 как обычные свойства компонента, а также может установить произвольные свойства типа Foo черезMap метод put(), где Foo - это просто какой-то другой определенный пользователем тип.

Проблема в том, что по умолчанию Gson сериализует экземпляры этих сгенерированных bean-компонентов, так что толькоMap записи включены в результирующую строку JSON, а явно определенные поля игнорируются.

Вот фрагмент кода, показывающий, как я использую Gson для сериализации объекта:

    private String serialize(Object obj) {
        return new Gson().toJson(obj);
    }

После отладки пути сериализации я вижу, что Gson выбирает свой внутренний MapTypeAdapterFactory для выполнения сериализации, что имеет смысл, поскольку только строки Map заканчиваются в строке JSON.Отдельно, если я сериализую bean-компонент, который НЕ подкласс HashMap, тогда Gson выбирает его внутренний ReflectiveTypeAdapterFactory.

Я думаю, что мне нужно, чтобы реализовать свой собственный адаптер пользовательского типа, который по существу объединяет функциональностьфабрики адаптеров типа Reflective и Map.Похоже ли это на хороший план?Кто-нибудь еще делал что-то похожее на это и, возможно, мог бы привести пример, чтобы начать меня?Это будет мой первый набег на Gson адаптеры нестандартного типа.

1 Ответ

0 голосов
/ 12 апреля 2019

Мы знаем, что по умолчанию Gson обрабатывает эти объекты как Map, поэтому мы можем использовать его для сериализации всех пар key-value и ручной сериализации остальных из них, используя отражение .

Простая реализация сериализатора может выглядеть следующим образом:

class MixedJsonSerializer implements JsonSerializer<Object> {

    @Override
    public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) {
        JsonObject json = serialiseAsMap(src, context);
        serialiseAsPojo(src, context, json);

        return json;
    }

    private JsonObject serialiseAsMap(Object src, JsonSerializationContext context) {
        return (JsonObject) context.serialize(src, Map.class);
    }

    private void serialiseAsPojo(Object src, JsonSerializationContext context, JsonObject mapElement) {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(src.getClass());
        for (Method method : methods) {
            if (shouldSerialise(method)) {
                final Object result = ReflectionUtils.invokeMethod(method, src);
                final String fieldName = getFieldName(method);
                mapElement.add(fieldName, context.serialize(result));
            }
        }
    }

    private boolean shouldSerialise(Method method) {
        final String name = method.getName();

        return method.getParameterCount() == 0 &&
                ReflectionUtils.USER_DECLARED_METHODS.matches(method) &&
                !IGNORED_METHODS.contains(name) &&
                (name.startsWith("is") || name.startsWith("get"));
    }

    private static final List<String> IGNORED_METHODS = Arrays.asList("isEmpty", "length"); //etc

    private String getFieldName(Method method) {
        final String field = method.getName().replaceAll("^(is|get)", "");

        return StringUtils.uncapitalize(field);
    }
}

Самая сложная часть - найти все геттеры POJO и вызвать их для данного объекта.Я использовал отражение API из библиотеки Spring только для примера.Ниже вы можете найти пример, как его использовать (я предполагаю, что все POJO классы расширяются HashMap):

import com.model.Foo;
import com.model.Pojo;
import com.model.Pojo1;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class GsonApp {

    public static void main(String[] args) {
        System.out.println("Pojo + Map: ");
        Pojo pojo = new Pojo();
        pojo.put("character1", new Foo("Morty", 15));
        pojo.put("character2", new Foo("Rick", 60));

        System.out.println(serialize(pojo));
        System.out.println();

        System.out.println("Map only: ");
        Pojo1 pojo1 = new Pojo1();
        pojo1.put("int1", 1);
        pojo1.put("int2", 22);
        System.out.println(serialize(pojo1));
        System.out.println();

        System.out.println("Pojo only:");
        System.out.println(serialize(new Pojo()));
        System.out.println();
    }

    private static final Gson gson = createGson();

    private static Gson createGson() {
        MixedJsonSerializer adapter = new MixedJsonSerializer();
        return new GsonBuilder()
                .setPrettyPrinting()
                // in case you have many classes you need to use reflection
                // to register adapter for each needed class.
                .registerTypeAdapter(Pojo.class, adapter)
                .registerTypeAdapter(Pojo1.class, adapter)
                .create();
    }

    private static String serialize(Object obj) {
        return gson.toJson(obj);
    }
}

Выше кода печатается:

Pojo + Map: 
{
  "character2": {
    "name": "Rick",
    "age": 60
  },
  "character1": {
    "name": "Morty",
    "age": 15
  },
  "prop1": "Value1",
  "ten": 10,
  "foo": {
    "name": "Test",
    "age": 123
  }
}

Map only: 
{
  "int2": 22,
  "int1": 1
}

Pojo only:
{
  "prop1": "Value1",
  "ten": 10,
  "foo": {
    "name": "Test",
    "age": 123
  }
}

Если вам нужноМногие классы, чтобы зарегистрировать их вручную, вы можете использовать отражение для сканирования заданных пакетов и зарегистрировать сериализатор для них.См .: Можете ли вы найти все классы в пакете, используя отражение?

...