Создать пример JSON из вложенных пустых объектов в Java - PullRequest
0 голосов
/ 24 мая 2018

Я пытаюсь создать утилиту для печати примеров структур JSON из любого заданного POJO.Я пытался Джексон и Гсон напечатать все поля из данного объекта.В качестве примеров я создал следующие три объекта:

public class Model {
    private String val1;
    private Child child;    
//getters and setters
}

public class Child {
    private String val2;
    private ArrayList<SubChild> subChildren;
//getters and setters
}

public class SubChild {
    private String val3;
//getters and setters
}

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

{
  "val1" : "",
  "child" : {
    "val2" : "",
    "subChildren" : [ {
      "val3" : ""
    } ]
  }
}

Это методы, которые я пытался напечатать в этих pojos вместе с их выводами, которые не совсем соответствуют моим потребностям

Джексон:

ObjectMapper map = new ObjectMapper().setSerializationInclusion(Include.ALWAYS);
Model testModel = new Model()
map.writerWithDefaultPrettyPrinter().writeValueAsString(testModel);

Вывод:

{
  "val1" : null,
  "child" : null
}

Gson:

Gson gson = builder.serializeNulls().setPrettyPrinting().create();
Model testModel = new Model();
gson.toJson(testModel);

Вывод:

{
  "val1" : null,
  "child" : null
}

Есть ли простой способ достижения моей цели без заполнениявсе дочерние поля?Я хотел бы иметь возможность использовать эту утилиту в общем классе, где я не знаю, какие методы вызывать для заполнения объектов пустыми значениями.

Ответы [ 2 ]

0 голосов
/ 25 мая 2018

Я сомневаюсь, что любая из библиотек будет поддерживать это "из коробки", потому что их единственная ответственность связана с сериализацией / десериализацией.Следовательно, чтобы заставить любой сериализатор работать так, как вы хотите, гораздо лучше создать объект-макет, а затем попытаться адаптировать стратегию-макет для конкретной библиотеки.

Прежде всего, давайте простосоздайте простой служебный класс для типов:

final class Types {

    private Types() {
    }

    static <T> Class<T> typeToClass(final Type type) {
        final Class<?> clazz;
        if ( type instanceof Class ) {
            clazz = (Class<?>) type;
        } else if ( type instanceof ParameterizedType ) {
            final ParameterizedType parameterizedType = (ParameterizedType) type;
            clazz = typeToClass(parameterizedType.getRawType());
        } else {
            throw new AssertionError(type);
        }
        @SuppressWarnings("unchecked")
        final Class<T> castClass = (Class<T>) clazz;
        return castClass;
    }

}

Приведенный выше метод отвечает только за разрешение класса из типа (по крайней мере, он пытается это сделать).Классы являются типами, но типы не обязательно являются классами.Ну, это их система типов Java.

Следующий класс отвечает за насмешку над объектом по типу.Это немного сложно, но комментарии проливают некоторый свет (ImmutableMap и ImmutableList взяты из Google Guava).

final class Mock {

    // Cache immutable primitives and wrappers. It's safe   
    private static final Optional<Byte> defaultByte = Optional.of((byte) 0);
    private static final Optional<Short> defaultShort = Optional.of((short) 0);
    private static final Optional<Integer> defaultInteger = Optional.of(0);
    private static final Optional<Long> defaultLong = Optional.of(0L);
    private static final Optional<Float> defaultFloat = Optional.of(0F);
    private static final Optional<Double> defaultDouble = Optional.of(0D);
    private static final Optional<Character> defaultCharacter = Optional.of('\u0000');
    private static final Optional<Boolean> defaultBoolean = Optional.of(false);
    private static final Optional<String> defaultString = Optional.of("");

    // This is a simple map that can return a value by a known type
    private static final Map<Class<?>, Optional<?>> defaultObjectsByIndex = ImmutableMap.<Class<?>, Optional<?>>builder()
            .put(byte.class, defaultByte).put(Byte.class, defaultByte)
            .put(short.class, defaultShort).put(Short.class, defaultShort)
            .put(int.class, defaultInteger).put(Integer.class, defaultInteger)
            .put(long.class, defaultLong).put(Long.class, defaultLong)
            .put(float.class, defaultFloat).put(Float.class, defaultFloat)
            .put(double.class, defaultDouble).put(Double.class, defaultDouble)
            .put(char.class, defaultCharacter).put(Character.class, defaultCharacter)
            .put(boolean.class, defaultBoolean).put(Boolean.class, defaultBoolean)
            .put(String.class, defaultString)
            .build();

    // Unlike the previous map, searching for an object takes a linear type
    // The first-best candidate will be used
    // The ifAssignable method is declared below
    private static final Collection<? extends Function<? super Type, Optional<?>>> defaultObjectsByFirstBest = ImmutableList.<Function<? super Type, Optional<?>>>builder()
            .add(ifAssignable(LinkedList.class, LinkedList::new))
            .add(ifAssignable(ArrayList.class, ArrayList::new))
            .add(ifAssignable(List.class, ArrayList::new))
            .add(ifAssignable(TreeSet.class, TreeSet::new))
            .add(ifAssignable(LinkedHashSet.class, LinkedHashSet::new))
            .add(ifAssignable(HashSet.class, HashSet::new))
            .add(ifAssignable(Set.class, HashSet::new))
            .add(ifAssignable(TreeMap.class, TreeMap::new))
            .add(ifAssignable(LinkedHashMap.class, LinkedHashMap::new))
            .add(ifAssignable(HashMap.class, HashMap::new))
            .add(ifAssignable(Map.class, HashMap::new))
            .build();

    private Mock() {
    }

    static <T> T create(
            final Type type,
            final Predicate<? super Type> isTypeSupported,
            final Predicate<? super Field> isFieldSupported,
            final Function<? super Class<T>, ? extends T> objectCreator,
            final BiFunction<Object, Type, Object> postProcess
    )
            throws Exception {
        return create(type, Mock::supplyDefaultInstance, isTypeSupported, isFieldSupported, objectCreator, postProcess);
    }

    static <T> T create(
            final Type type, // Used to instantiate an object 
            final Function<? super Type, Optional<?>> defaultInstanceSupplier, // Used in order to supply a default object by type
            final Predicate<? super Type> isTypeSupported, // Not all types can be serialized
            final Predicate<? super Field> isFieldSupported, // Not all fields can be serialized
            final Function<? super Class<T>, ? extends T> objectCreator, // If no any default object can be supplied, try to ask for it from elsewhere
            final BiFunction<Object, Type, Object> postProcess // This is what is what used to post-process the result object
    )
            throws Exception {
        // Not something we want to support?
        if ( !isTypeSupported.test(type) ) {
            return null;
        }
        // Check if we can provide a default value 
        final Optional<?> maybeT = defaultInstanceSupplier.apply(type);
        if ( maybeT.isPresent() ) {
            @SuppressWarnings("unchecked")
            final T castT = (T) postProcess.apply(maybeT.get(), type);
            return castT;
        }
        final Class<T> clazz = Types.typeToClass(type);
        // No? Then let's try instantiate it
        final T newT = objectCreator.apply(clazz);
        // And iterate it from bottom to top to super classes (java.lang.Object does not have fields)
        for ( Class<?> i = clazz; i != null && i != Object.class; i = i.getSuperclass() ) {
            for ( final Field field : i.getDeclaredFields() ) {
                if ( isFieldSupported.test(field) ) {
                    field.setAccessible(true);
                    // Recursively do the same for all the fields
                    final Object value = create(field.getGenericType(), defaultInstanceSupplier, isTypeSupported, isFieldSupported, objectCreator, postProcess);
                    field.set(newT, value);
                }
            }
        }
        // And then do post-processing for these "special" types 
        @SuppressWarnings("unchecked")
        final T castNewT = (T) postProcess.apply(newT, type);
        return castNewT;
    }

    static Optional<?> supplyDefaultInstance(final Type type) {
        final Optional<?> defaultValueFromIndex = defaultObjectsByIndex.get(type);
        if ( defaultValueFromIndex != null ) {
            return defaultValueFromIndex;
        }
        return defaultObjectsByFirstBest
                .stream()
                .map(resolver -> resolver.apply(type))
                .filter(Optional::isPresent)
                .findFirst()
                .orElse(Optional.empty());
    }

    private static Function<? super Type, Optional<?>> ifAssignable(final Class<?> expectedClass, final Supplier<?> defaultObject) {
        return actualClass -> expectedClass.isAssignableFrom(Types.typeToClass(actualClass))
                ? Optional.of(defaultObject.get())
                : Optional.empty();
    }

}

Как только вы все это получите, вы можете получить специализацию Gson и выполнять постобработку, например, добавлять фиктивные элементы в коллекции (коллекции по умолчанию являются и должны быть пустыми).

public final class Q50515517 {

    private Q50515517() {
    }

    private static final Gson gson = new GsonBuilder()
            .serializeNulls()
            .setPrettyPrinting()
            .disableHtmlEscaping()
            .create();

    // Gson can avoid use of constructors
    private static final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();

    public static void main(final String... args)
            throws Exception {
        gson.toJson(create(Object3.class, gson, Q50515517::customPostProcess), Object3.class, System.out);
    }

    // Here we make some Gson adaptations
    private static <T> T create(final Type type, final Gson gson, final BiFunction<Object, Type, Object> postProcess)
            throws Exception {
        final Excluder excluder = gson.excluder();
        final Predicate<? super Type> isClassSupported = t -> !excluder.excludeClass(Types.typeToClass(t), true);
        final Predicate<? super Field> isFieldSupported = field -> !excluder.excludeField(field, true);
        return Mock.create(type, Mock::supplyDefaultInstance, isClassSupported, isFieldSupported, Q50515517::createUnsafely, postProcess);
    }

    private static <T> T createUnsafely(final Class<T> clazz) {
        try {
            return unsafeAllocator.newInstance(clazz);
        } catch ( final Exception ex ) {
            throw new RuntimeException(ex);
        }
    }

    private static Object customPostProcess(final Object object, final Type type) {
        if ( object instanceof Collection ) {
            if ( type instanceof ParameterizedType ) {
                @SuppressWarnings("unchecked")
                final Collection<Object> collection = (Collection<Object>) object;
                final ParameterizedType parameterizedType = (ParameterizedType) type;
                final Type elementType = parameterizedType.getActualTypeArguments()[0];
                try {
                    final Object newElement = create(elementType, gson, Q50515517::customPostProcess);
                    collection.add(newElement); // This is where we add a mock element to the collection
                    return object;
                } catch ( final Exception ex ) {
                    throw new RuntimeException(ex);
                }
            }
        }
        return object;
    }

}

Таким образом, это решение поддерживает:

  • Классыбез конструкторов по умолчанию
  • Поля иерархии классов объектов
  • Стратегии исключения
  • Интерфейсы
  • etc

Примеры классов из вышеприведенного теста:

class Object1 {
    String s1;
}

class Object2
        extends Object1 {
    String s2;
}

class Object3
        extends Object2 {
    String s3;
    List<Object4> lo4;
}

class Object4 {
    String s4a;
    String s4b;
}

Вывод:

{
  "s3": "",
  "lo4": [
    {
      "s4a": "",
      "s4b": ""
    }
  ],
  "s2": "",
  "s1": ""
}
0 голосов
/ 24 мая 2018

Я не вижу, как это было бы возможно.Что если у вас есть Long как поле?Джексон не знал бы, что там делать.То, что сделали Джексон и Гсон, правильно, и они напечатали null.

Что вы могли бы сделать, так это написать утилиту для установки полей вручную.Однако вам придется обрабатывать разные типы соответственно.Примерно так получается то, о чем вы просили, но только для List:

public static void main(String args[]) throws IOException, IllegalAccessException {
        ObjectMapper map = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.ALWAYS);
        Model testModel = new Model();

        instantiateFields(testModel);

        String result = map.writerWithDefaultPrettyPrinter().writeValueAsString(testModel);
        System.out.println(result);
    }

    private static void instantiateFields(Object o) throws IllegalAccessException {
        Field[] fields = o.getClass().getDeclaredFields();

        for (Field field : fields) {
            field.setAccessible(true);

            if (field.get(o) == null) {
                Type type = field.getType();

                try {
                    Class<?> clazz = (Class<?>) type;
                    Object instance = clazz.newInstance();

                    if (List.class.isAssignableFrom(clazz)) {
                        instantiateList(clazz, field, instance);
                    }

                    field.set(o, instance);
                    instantiateFields(instance);

                } catch (ClassCastException | InstantiationException e) {
                    // Handle this or leave field null
                }
            }
        }
    }

    private static void instantiateList(Class<?> clazz, Field field, Object instance) throws IllegalAccessException, InstantiationException {
        ParameterizedType listType = (ParameterizedType) field.getGenericType();
        Class<?> listClass = (Class<?>) listType.getActualTypeArguments()[0];

        Object listTypeInstance = listClass.newInstance();

        instantiateFields(listTypeInstance);

        List<Object> list = (List<Object>) instance;
        list.add(listTypeInstance);
    }

Получив следующий вывод:

{
  "val1" : "",
  "child" : [ {
    "val2" : "",
    "subChildren" : [ {
      "val3" : ""
    } ]
  } ]
}

Надеюсь, это поможет.

...