创建一个自定义Jackson Jsonserializer und jsondeserializer用于映射值
#api #java #springboot #jackson

对于我的一系列文章,我还想看看如何使用Jackson实现此要求。

第一篇文章中的第一段“ The requirements and history”介绍了Emarsys重写有效载荷值的要求。

所需的软件包

  • 杰克逊
    • com.fasterxml.jackson.core:Jackson-Databin
    • com.fasterxml.jackson.datatype:jackson-datatype-jsr310
  • junit
    • org.junit.jupiter:junit-jupiter-api
    • org.junit.jupiter:junit-jupiter-engine
  • jsonunit
    • net.javacrumbs.json-unit:json-unit-assertj
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.14.2</version>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.9.2</version>
</dependency>
<dependency>
<dependency>    
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.9.2</version>
</dependency>
<dependency>
    <groupId>net.javacrumbs.json-unit</groupId>
    <artifactId>json-unit-assertj</artifactId>
    <version>2.37.0</version>
    <scope>test</scope>
</dependency>

自定义jsonserializer和jsondeserializer的最小结构

要求解映射emarsys值的要求,需要自定义的jsonserializer和jsondeserializer。我称这些 mappingValueserializer mappingValueDeserializer

以下是自定义MappingValueSerializerMappingValueDeserializer的最小结构:

@JsonSerialize(using = MappingValueSerializer.class)
@JsonDeserialize(using = MappingValueDeserializer.class)
private String fieldName;

public class MappingValueSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString("serialized: " + value);
    }
}

public class MappingValueDeserializer extends JsonDeserializer<String> {
    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException  {
        String value = jsonParser.getText();
        return "deserialized: " + value;
    }
}

在contactdto中,salutationmarketingInformation必须重写值。

字段/方向 序列化(t->字符串) deperialize(string-> t)
称呼 “女性” - >“ 2” “ 2” - >“女性”
营销信息 true->“ 1” “ 1” - > true

对于序列化过程,它是fieldValueId(字符串)和避难所的过程salutation的类型字符串和marketingInformation的类型布尔。

因此,如果要进行映射,则需要一个jsonserialializer来编写salutationmarketingInformation的fieldValueId(string)和jsondeserialializer来设置stalutation(string)的值(string)和marketingInformation(boolean)。

自定义类型

但是,我只想拥有一个可以处理字符串,布尔值和将来其他类型的jsondeserialializer。为此,我创建了自己的类型MappingValue<>。最重要的是,我可以使用此自定义仿制药运输所有类型。

package com.microservice.crm.serializer;

public class MappingValue<T> {
    T value;

    public MappingValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return this.value;
    }
}

联系人

首先,所有字段和注释的完整联系人中的所有联系人。我将解释以下各个注释。

package com.microservice.crm.fixtures;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.microservice.crm.annotation.*;
import com.microservice.crm.serializer.MappingValue;
import com.microservice.crm.serializer.MappingValueDeserializer;
import com.microservice.crm.serializer.MappingValueSerializer;

import java.time.LocalDate;

@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    getterVisibility = JsonAutoDetect.Visibility.NONE,
    setterVisibility = JsonAutoDetect.Visibility.NONE,
    isGetterVisibility = JsonAutoDetect.Visibility.NONE
)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ContactDto {

    @JsonProperty("1")
    private String firstname;

    @JsonProperty("2")
    private String lastname;

    @JsonProperty("3")
    private String email;

    @JsonProperty("4")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @JsonSerialize(using = LocalDateSerializer.class)
    @JsonDeserialize(using = LocalDateDeserializer.class)
    private LocalDate birthday;

    @JsonProperty("46")
    @MappingTable(map = "{\"1\": \"MALE\", \"2\": \"FEMALE\", \"6\": \"DIVERS\"}")
    @JsonSerialize(using = MappingValueSerializer.class)
    @JsonDeserialize(using = MappingValueDeserializer.class)
    private MappingValue<String> salutation;

    @JsonProperty("100674")
    @MappingTable(map = "{\"1\": true, \"2\": false}")
    @JsonSerialize(using = MappingValueSerializer.class)
    @JsonDeserialize(using = MappingValueDeserializer.class)
    private MappingValue<Boolean> marketingInformation;

    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public void setBirthday(LocalDate birthday) {
        this.birthday = birthday;
    }

    public String getSalutation() {
        return salutation.getValue();
    }

    public void setSalutation(String salutation) {
        this.salutation = new MappingValue<>(salutation);
    }

    public Boolean getMarketingInformation() {
        return marketingInformation.getValue();
    }

    public void setMarketingInformation(Boolean marketingInformation) {
        this.marketingInformation = new MappingValue<>(marketingInformation);
    }
}

在田野上阅读和写作

杰克逊的对象管理员默认情况下在突变器(setter)和登录器(getter,isser)上写入并读取。

对于salutationmarketingInformation的突变器和登录器,我想定义类型字符串或布尔值。

您可以使用注释指示杰克逊仅在字段上读写,因此我们可以在内部使用自定义类型MappingValue <>。因此,阅读和写作过程发生在字段上,我们可以为salutationmarketingInformation的突变器和访问者定义字符串和布尔。

@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    getterVisibility = JsonAutoDetect.Visibility.NONE,
    setterVisibility = JsonAutoDetect.Visibility.NONE,
    isGetterVisibility = JsonAutoDetect.Visibility.NONE
)

fieldids

使用@JsonProperty可以非常容易地定义野战。

@JsonProperty("123")

定义Custom Jsonserializer和Jsondeserializer

可以用@JsonSerialize@JsonDeserialize定义自定义的jsonserializer(MappingValueSerializer)和jsondeserializer(MappingValueDeserializer)。

@JsonSerialize(using = MappingValueSerializer.class)
@JsonDeserialize(using = MappingValueDeserializer.class)

跳过零值

null为值的字段不应序列化。这是因为发送的字段也已更新。注释@JsonInclude可用于此。

@JsonInclude(JsonInclude.Include.NON_NULL)

忽略未知属性

Emarsys总是返回所有字段,以进行响应中的联系。我只希望映射contactdto中定义的字段,以便没有例外。注释@JsonIgnoreProperties可用于此:

@JsonIgnoreProperties(ignoreUnknown = true)

自定义注释@MappingTable可用于示意图

必须在MappingValueserizer和MappingValueDeserializer中使用salutationmarketingInformation的fieldValueids映射。

为此,我创建了一个自定义注释@MappingTable,它将在mappingvalueserializer和mappingValueDeserializer中读取。

package com.microservice.crm.annotation;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface MappingTable {
    String map() default "{}";
}

@MappingTable在注释中定义为蒙版的json(字符串)。

只有以下类型的注释:

  • 原始类型
  • 字符串
  • 枚举
  • class(class <?>,class <?扩展/super t>)
  • 上述数组(原始,枚举,字符串或类的数组[])
  • 另一个注释。

请参阅Stackoverflow上的此conversation

MappingValueserializer和MappingValueDeserializer

要读取在字段中定义的示波器,必须为MappingValueSerializer实现接口ContextualSerializerMappingValueDeserializer的接口ContextualDeserializer

使用createContextual(),可以访问该属性,并且通过BeanProperty可以获取注释,并且可以读取示意图。

MappingTableDataReader将json转换为哈希图。

MappingValueserializer

MappingValueSerializer中,对于salutation“女性”映射到“ 2”,而marketingInformation ture为“ 1”,这就是为什么fieldValueid用jsonGenerator.writeString()编写的原因。

package com.microservice.crm.serializer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.microservice.crm.annotation.MappingTableDataReader;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class MappingValueSerializer extends JsonSerializer<MappingValue<?>> implements ContextualSerializer {

    private final HashMap<String, ?> data;

    public MappingValueSerializer() {
        this(null);
    }

    public MappingValueSerializer(HashMap<String, ?> data) {
        this.data = data;
    }

    @Override
    public void serialize(MappingValue<?> value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        String fieldId = this.data.entrySet().stream()
                .filter(e -> e.getValue().equals(value.getValue()))
                .map(Map.Entry::getKey)
                .findFirst()
                .orElse(null);
        jsonGenerator.writeString(fieldId);
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
        return new MappingValueSerializer(
                new MappingTableDataReader().getMap(property)
        );
    }
}

MappingValueDeserializer

MappingValueDeserializer中,映射发生在向后进行。在这里,必须相应地映射salutationmarketingInformation的fieldValueid。对于salutation“ 2”到“女性”(string)和marketingInformation“ 1”到true(boolean)。

package com.microservice.crm.serializer;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.microservice.crm.annotation.MappingTableDataReader;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class MappingValueDeserializer extends JsonDeserializer<MappingValue<?>> implements ContextualDeserializer {

    private final String[] supportedTypes = {"String", "Boolean"};

    private final HashMap<String, ?> data;

    private final Type type;

    public MappingValueDeserializer() {
        this(null, null);
    }

    public MappingValueDeserializer(HashMap<String, ?> data, Type type) {
        this.data = data;
        this.type = type;
    }

    @Override
    public MappingValue<?> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        String value = jsonParser.getText();
        String simpleName = ((SimpleType) this.type).getBindings().getTypeParameters().get(0).getRawClass().getSimpleName();

        if (Arrays.stream(supportedTypes).noneMatch(simpleName::equalsIgnoreCase)) {
            throw new IOException(String.format("Type \"%s\" is currently not supported", simpleName));
        }

        return new MappingValue<>(this.data.entrySet().stream()
                .filter(e -> e.getKey().equals(value))
                .map(Map.Entry::getValue)
                .findFirst()
                .orElse(null));
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        return new MappingValueDeserializer(
                new MappingTableDataReader().getMap(property),
                property.getType()
        );
    }
}

测试

要检查实现,我们仍然需要测试。要比较JSON,我使用json-unit-assertj的assertThatJson()

package com.microservice.crm.serializer;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.microservice.crm.fixtures.ContactDto;
import net.javacrumbs.jsonunit.core.Option;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.time.LocalDate;

import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class MappingTableSerializerDeserializerTest {

    String emarsysPayload = """
            {
                "1": "Jane",
                "2": "Doe",
                "3": "jane.doe@example.com",
                "4": "1989-11-09",
                "46": "2",
                "100674": "1"
            }
            """;

    @Test
    void serialize() throws IOException {
        ContactDto contact = new ContactDto();
        contact.setSalutation("FEMALE");
        contact.setFirstname("Jane");
        contact.setLastname("Doe");
        contact.setEmail("jane.doe@example.com");
        contact.setBirthday(LocalDate.of(1989, 11, 9));
        contact.setMarketingInformation(true);
        String json = new ObjectMapper().writeValueAsString(contact);
        assertThatJson(this.emarsysPayload.trim())
                .when(Option.IGNORING_ARRAY_ORDER)
                .isEqualTo(json);
    }

    @Test
    void deserialize() throws IOException {
        ContactDto contact = new ObjectMapper().readValue(this.emarsysPayload.trim(), ContactDto.class);
        assertEquals("FEMALE", contact.getSalutation());
        assertEquals("Jane", contact.getFirstname());
        assertEquals("Doe", contact.getLastname());
        assertEquals("jane.doe@example.com", contact.getEmail());
        assertEquals(LocalDate.of(1989, 11, 9), contact.getBirthday());
        assertTrue(contact.getMarketingInformation());
    }
}

全部

映射图定义为注释@MappingTable的蒙版JSON。这意味着我们不能在任何其他情况下使用数据。由于注释不支持hashmap,因此无法定义哈希图。例如,解决方案是使用枚举类。

我将在以后的文章中如何解决这一问题。

链接

Full example on github