Jackson Custom Serializer แสดงบริบทเดียวกันสำหรับ 2 ฟิลด์ที่แตกต่างกันระหว่าง Json Serialization

ฉันกำลังพยายามสร้าง JSON ตามคลาส Object POJO ของฉัน สำหรับบางฟิลด์ ฉันต้องการใช้ CustomSerializer เนื่องจากฉันต้องการสร้างฟิลด์ตามความต้องการของฉัน ดังนั้นฉันจึงสร้าง CustomSerializer.class

CustomSerializer จะถูกเรียกโดย 2 ฟิลด์ที่แตกต่างกันใน 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 ของฉันซึ่งจะถูกเรียกโดย 2 ฟิลด์ (ส่วนขยายและไม่มีส่วนขยาย) ในระหว่างการสร้าง 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 หรือสตริงว่าง

ฉันแค่อยากทราบว่านี่เป็นข้อบกพร่องในส่วนของ Jackson หรือไม่ หรือมีวิธีแก้ไขใดที่ฉันสามารถลองแยกความแตกต่างได้ว่าฟิลด์ใดที่กำลังเรียกไปยัง CustomSerializer ของฉัน

ความช่วยเหลือใด ๆ ที่จะได้รับการชื่นชมจริงๆ ขอบคุณ.


person BATMAN_2008    schedule 10.07.2021    source แหล่งที่มา


คำตอบ (2)


A. สร้าง Map serialisers สองตัวโดยที่ตัวหนึ่งสร้างวัตถุภายนอกและอีกตัวไม่สร้าง

ข้อดี:

  • ง่ายต่อการปฏิบัติ
  • ง่ายต่อการทดสอบ
  • ชั้นเรียนหนึ่งทำสิ่งเดียวเท่านั้น
  • Map serialiser ที่ไม่ได้สร้างวัตถุภายนอกสามารถถูกแทนที่ด้วย Map serialiser ที่กำหนดเอง (ถ้าเป็นไปได้)

จุดด้อย:

  • อาจเป็นปัญหาได้หากจำเป็นต้องแชร์สถานะ
  • รหัสที่อาจซ้ำกัน

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