Пользовательский сериализатор Джексона показывает один и тот же контекст для двух разных полей во время сериализации Json.

Я пытаюсь создать JSON на основе моего класса объектов POJO. Для некоторых полей я хотел бы использовать CustomSerializer, поскольку я хотел бы создать поля в соответствии с моим требованием. Следовательно, я создал файл CustomSerializer.class.

CustomSerializer будет вызываться двумя разными полями в моем POJO, и я хотел бы обрабатывать вещи по-разному в зависимости от того, какое поле вызывает вызов. Для одного из полей (extensions) я хотел бы иметь fieldName, а для другого поля (withoutExtensions) я не хочу иметь fieldname в своем JSON.

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

Следующие примеры кода дадут больше ясности по проблеме, с которой я столкнулся:

Customer Класс POJO, используемый для сериализации JSON:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
@NoArgsConstructor
public class Customer {
    private String isA;
    private String name;

    @JsonSerialize(using = CustomSerializer.class)
    private Map<String, Object> extensions = new HashMap<>();

    private Map<String, Object> withoutExtensions = new HashMap<>();

    @JsonAnyGetter
    @JsonSerialize(using = CustomSerializer.class)
    public Map<String, Object> getWithoutExtensions() {
        return withoutExtensions;
    }

}

Ниже приведен мой CustomSerializer, который будет вызываться двумя полями (расширениями и без расширений) во время создания JSON:

public class CustomSerializer extends JsonSerializer<Map<String, Object>> {

    @Override
    public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) {
        //I would like to create the outer object for "Extensions" but do not want to create outer object for "WithoutExtensions"
        
         System.out.println(gen.getOutputContext().getCurrentName());

        //In my case for both "Extensions" and "WithoutExtensions" i get the "currentName" as "Extensions" how can I ensure which field is calling this sealizer at
        // present
    }
}

Ниже приведен мой класс Main, который создаст JSON:

public class Main {
    public static void main(String[] args) throws JsonProcessingException {
        final ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        final Customer customer = new Customer();

        customer.setName("Jackson");

        Map<String, Object> extensions = new HashMap<>();
        extensions.put("WithObject", "With");
        customer.setExtensions(extensions);

        Map<String, Object> withoutExtensions = new HashMap<>();
        extensions.put("WithoutObject", "Without");
        customer.setWithoutExtensions(withoutExtensions);

        final String eventAsJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
        System.out.println(eventAsJson);
    }
}

Как мы видим, когда я запускаю приложение, CustomSerializer будет печатать extensions в обоих случаях. Я считаю, что он должен печатать extensions только один раз, а в следующем случае он должен предоставить либо withoutExtensions, либо пустую строку.

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

Любая помощь могла бы быть полезна. Спасибо.


person BATMAN_2008    schedule 10.07.2021    source источник


Ответы (2)


A. Создайте два сериализатора Map, один из которых создает внешний объект, а другой нет.

Плюсы:

  • Легко реализовать
  • Легко проверить
  • Один класс делает ровно одну вещь
  • Map сериализатор, который не создает внешний объект, может быть заменен пользовательским Map сериализатором (если возможно)

Минусы:

  • Может быть проблематично, если им нужно разделить состояние.
  • Возможно дублированный код

B. Реализуйте ContextualSerializer интерфейс

Плюсы:

  • Можно настроить для каждого поля отдельно
  • Может поделиться состоянием, если это необходимо. Пользователь контролирует количество создаваемых экземпляров.

Минусы:

  • Делает более 1 вещи
  • Может быть легко слишком сложным

Примеры:

person Michał Ziober    schedule 10.07.2021
comment
Большое спасибо за ответ. Это действительно помогло мне, и я выбрал вариант B, так как мне казалось, что проще всего уменьшить избыточный код. Я принимаю этот ответ и еще раз спасибо. Хорошего дня :) - person BATMAN_2008; 10.07.2021

Основываясь на ответе @Michal, я изменил код, и он работал для обоих сценариев. Публикация полного примера кода, так как он может быть полезен кому-то в будущем:

Customer.class добавил @Extensions в обязательные поля:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
@NoArgsConstructor
public class Customer {
    private String isA;
    private String name;

    @JsonSerialize(using = CustomSerializer.class)
    @Extensions(extension = "extensions")
    private Map<String, Object> extensions = new HashMap<>();

    private Map<String, Object> withoutExtensions = new HashMap<>();

    @JsonAnyGetter
    @JsonSerialize(using = CustomSerializer.class)
    @Extensions(extension = "withoutExtensions")
    public Map<String, Object> getWithoutExtensions() {
        return withoutExtensions;
    }

}

CustomSerializer:

@NoArgsConstructor
public class CustomSerializer extends JsonSerializer<Map<String, Object>> implements ContextualSerializer {

    private String context = "";

    public CustomSerializer(String context) {
        this.context = context;
    }


    @Override
    public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) {
        if (this.context.equals("extensions")) {
            System.out.println("Extensions : " + this.context);
        } else if (this.context.equals("withoutExtensions")) {
            System.out.println("Without Extensions : " + this.context);
        }
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        Extensions extensions = beanProperty.getAnnotation(Extensions.class);
        if (extensions != null) {
            return new CustomSerializer(extensions.extension());
        }
        return this;
    }
}


@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface Extensions {
    String extension();
}
person BATMAN_2008    schedule 10.07.2021