Spring Boot Jackson 自定义JSON序列化和反序列化

5 篇文章 0 订阅
1 篇文章 0 订阅

源码放在了GitHub, 地址: https://github.com/galaxy-sea/galaxy-blogs/tree/master/code/json-serialize

源码放在了GitHub, 地址: https://github.com/galaxy-sea/galaxy-blogs/tree/master/code/json-serialize

源码放在了GitHub, 地址: https://github.com/galaxy-sea/galaxy-blogs/tree/master/code/json-serialize

1. 序列化

1.1. JsonSerializer序列化父类

在 jackson 自定义序列化中提供了两个抽象类,com.fasterxml.jackson.databind.JsonSerializercom.fasterxml.jackson.databind.ser.std.StdSerializer。一般,我们可以直接继承JsonSerializer抽象类就已经足够满足我们的自定义序列化需求了。

下面我们看一下jackson的StringSerializer源码, 发现自定义序列化是如此的简单,基本没有什么难受难度

@JacksonStdImpl
public final class StringSerializer extends StdScalarSerializer<Object>{

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeString((String) value);
    }

    ...
}

1.2. ContextualSerializer序列化上下文

在序列化的时候我们难免需要获取字段或者类的一些信息(字段名, 字段类型, 字段注解, 类名, 类注解 …)这个使用我们就需要使用jackson提供的
com.fasterxml.jackson.databind.ser.ContextualSerializer

下面我们看一下com.fasterxml.jackson.databind.ser.std.DateTimeSerializerBase的源码, 看他对com.fasterxml.jackson.annotation.JsonFormat注解的操作
com.fasterxml.jackson.annotation.JsonFormat是平常开发中对序列化进行格式化

public abstract class DateTimeSerializerBase<T> extends StdScalarSerializer<T> implements ContextualSerializer{
@Override
    public JsonSerializer<?> createContextual(SerializerProvider serializers,
            BeanProperty property) throws JsonMappingException
    {
        // Note! Should not skip if `property` null since that'd skip check
        // for config overrides, in case of root value
        JsonFormat.Value format = findFormatOverrides(serializers, property, handledType());
        if (format == null) {
            return this;
        }
        // Simple case first: serialize as numeric timestamp?
        JsonFormat.Shape shape = format.getShape();
        if (shape.isNumeric()) {
            return withFormat(Boolean.TRUE, null);
        }

        // 08-Jun-2017, tatu: With [databind#1648], this gets bit tricky..
        // First: custom pattern will override things
        if (format.hasPattern()) {
            final Locale loc = format.hasLocale() ? format.getLocale() : serializers.getLocale();
            SimpleDateFormat df = new SimpleDateFormat(format.getPattern(), loc);
            TimeZone tz = format.hasTimeZone() ? format.getTimeZone() : serializers.getTimeZone();
            df.setTimeZone(tz);
            return withFormat(Boolean.FALSE, df);
        }

        // Otherwise, need one of these changes:
        final boolean hasLocale = format.hasLocale();
        final boolean hasTZ = format.hasTimeZone();
        final boolean asString = (shape == JsonFormat.Shape.STRING);

        if (!hasLocale && !hasTZ && !asString) {
            return this;
        }

        DateFormat df0 = serializers.getConfig().getDateFormat();
        // Jackson's own `StdDateFormat` is quite easy to deal with...
        if (df0 instanceof StdDateFormat) {
            StdDateFormat std = (StdDateFormat) df0;
            if (format.hasLocale()) {
                std = std.withLocale(format.getLocale());
            }
            if (format.hasTimeZone()) {
                std = std.withTimeZone(format.getTimeZone());
            }
            return withFormat(Boolean.FALSE, std);
        }

        // 08-Jun-2017, tatu: Unfortunately there's no generally usable
        //    mechanism for changing `DateFormat` instances (or even clone()ing)
        //    So: require it be `SimpleDateFormat`; can't config other types
        if (!(df0 instanceof SimpleDateFormat)) {
             serializers.reportBadDefinition(handledType(), String.format("Configured `DateFormat` (%s) not a `SimpleDateFormat`; cannot configure `Locale` or `TimeZone`", df0.getClass().getName()));
        }
        SimpleDateFormat df = (SimpleDateFormat) df0;
        if (hasLocale) {
            // Ugh. No way to change `Locale`, create copy; must re-crete completely:
            df = new SimpleDateFormat(df.toPattern(), format.getLocale());
        } else {
            df = (SimpleDateFormat) df.clone();
        }
        TimeZone newTz = format.getTimeZone();
        boolean changeTZ = (newTz != null) && !newTz.equals(df.getTimeZone());
        if (changeTZ) {
            df.setTimeZone(newTz);
        }
        return withFormat(Boolean.FALSE, df);
    }

}

 ......

1.3. 自定义序列化注册Jackson

虽然注册的方式很多,但是主要是三种

  • SimpleModule
  • @JsonSerialize(推荐)
  • CustomSerializerFactory(新版已删除)

1.3.1. SimpleModule

com.fasterxml.jackson.databind.module.SimpleModule的注册太麻烦了

    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addSerializer(Panda.class, new PandSerializer);
    mapper.registerModule(module);
    String jsonString = mapper.writeValueAsString(new Panda());

1.3.2. @JsonSerialize 注解

通过注解的方式注册序列化方法,对使用体验来说非常友好。但是会存在两个问题:

首先,提供的序列化类的功能比较简单,然后,就是提供的粒度比较小,然后你想在全局的某个类型上都是有自定义序列化,则需要对每个对象类型上都需要标记注解。

@JsonSerialize 注解的配置参数有很多种,但是我们只需要using这种来指明具体的自定义序列化类就可以了。

1.3.3. CustomSerializerFactory

后面章节手动实现一个SerializerFactory

2. 反序列化

反序列化基本上和序列化差不多

2.1. JsonDeserializer序列化父类

自定反义序列化中提供了两个抽象类,com.fasterxml.jackson.databind.JsonDeserializercom.fasterxml.jackson.databind.deser.std.StdDeserializer。一般,我们可以直接继承JsonDeserializer抽象类就已经足够满足我们的自定义反序列化需求了。

下面我们看一下jackson的StringDeserializer源码, 反序列化比序列化要复杂一点

public class StringDeserializer extends StdScalarDeserializer<String>{
    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
    {
        if (p.hasToken(JsonToken.VALUE_STRING)) {
            return p.getText();
        }
        JsonToken t = p.getCurrentToken();
        // [databind#381]
        if (t == JsonToken.START_ARRAY) {
            return _deserializeFromArray(p, ctxt);
        }
        // need to gracefully handle byte[] data, as base64
        if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {
            Object ob = p.getEmbeddedObject();
            if (ob == null) {
                return null;
            }
            if (ob instanceof byte[]) {
                return ctxt.getBase64Variant().encode((byte[]) ob, false);
            }
            // otherwise, try conversion using toString()...
            return ob.toString();
        }
        // allow coercions for other scalar types
        // 17-Jan-2018, tatu: Related to [databind#1853] avoid FIELD_NAME by ensuring it's
        //   "real" scalar
        if (t.isScalarValue()) {
            String text = p.getValueAsString();
            if (text != null) {
                return text;
            }
        }
        return (String) ctxt.handleUnexpectedToken(_valueClass, p);
    }

    ......
}

2.2. ContextualDeserializer反序列化上下文

查看com.fasterxml.jackson.datatype.jsr310.deser.JSR310DateTimeDeserializerBasecom.fasterxml.jackson.annotation.JsonFormat的上下文关系

public abstract class JSR310DateTimeDeserializerBase<T> extends JSR310DeserializerBase<T> implements ContextualDeserializer{
    
    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException{
        JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
        JSR310DateTimeDeserializerBase<?> deser = this;
        if (format != null) {
            if (format.hasPattern()) {
                final String pattern = format.getPattern();
                final Locale locale = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
                DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
                if (acceptCaseInsensitiveValues(ctxt, format)) {
                    builder.parseCaseInsensitive();
                }
                builder.appendPattern(pattern);
                DateTimeFormatter df;
                if (locale == null) {
                    df = builder.toFormatter();
                } else {
                    df = builder.toFormatter(locale);
                }
                //Issue #69: For instant serializers/deserializers we need to configure the formatter with
                //a time zone picked up from JsonFormat annotation, otherwise serialization might not work
                if (format.hasTimeZone()) {
                    df = df.withZone(format.getTimeZone().toZoneId());
                }
                deser = deser.withDateFormat(df);
            }
            // 17-Aug-2019, tatu: For 2.10 let's start considering leniency/strictness too
            if (format.hasLenient()) {
                Boolean leniency = format.getLenient();
                if (leniency != null) {
                    deser = deser.withLeniency(leniency);
                }
            }
            // any use for TimeZone?
        }
        return deser;
    }
    
}

2.3. 反序列化注册

2.3.1. SimpleModule

com.fasterxml.jackson.databind.module.SimpleModule的注册太麻烦了

        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Panda.class, new PandDeserializer);
        mapper.registerModule(module);
        Panda panda = objectMapper.readValue(jsonString, Panda.class);

2.3.2. @Deserializer注解

通过注解的方式注册序列化方法,对使用体验来说非常友好。但是会存在两个问题:

首先,提供的序列化类的功能比较简单,然后,就是提供的粒度比较小,然后你想在全局的某个类型上都是有自定义序列化,则需要对每个对象类型上都需要标记注解。

@JsonDeserializer 注解的配置参数有很多种,但是我们只需要using这种来指明具体的自定义序列化类就可以了。

2.3.3. CustomDeserializerFactory

后面章节手动实现一个DeserializerFactory

3. 实践操作

业务分析, 只要字段上面注解了@ValuePrefix注解,就需要序列化时让value前加上前缀,

所以我们需要定义一个@ValuePrefix注解用于注册JsonSerializeJsonDeserialize

要需要定义一个bean用于序列化和反序列化, 详细代码如下

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})

// 指定序列化
@JsonSerialize(using = ValuePrefixJsonSerializerFactory.class)
// 指定反序列化
@JsonDeserialize(using = ValuePrefixJsonDeserializerFactory.class)
public @interface ValuePrefix {
    String prefix() default "前缀";
}
public class Panda {

    @ValuePrefix(prefix = "img前缀")
    private String img;
    
    @ValuePrefix(prefix = "imgList前缀")
    private List<String> imgList;

    @ValuePrefix(prefix = "imgArray前缀")
    private String[] imgArray;

    @ValuePrefix(prefix = "imgMap前缀")
    private Map<String,String> imgMap;

    // getter and setter
}

4. JsonSerializer

4.1. 自定义StringJsonSerializer

jackson 提供了一个String序列化类com.fasterxml.jackson.databind.ser.std.StringSerializer, 但是StringSerializer类被final修饰了不能被继承, 但是无妨我们学习, 我们可以查看StringSerializer.serialize()方法,惊奇的发现, 我草这个方法好简单,

下面我们就自定义一个StringJsonSerializer类

    /**
    * @author galaxy
    */
    class StringPrefixJsonSerializer extends JsonSerializer<String> {
        @Override
        public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            if (value == null) {
                serializers.defaultSerializeNull(gen);
            } else {
                gen.writeString(prefix + value);
            }
        }
    }

偷懒的第一步, 简直完美呀, 这里我们就对自定义的String序列化完成了, 没有什么代码含量是不是, 直接看源码复制一下就可以

4.2. 自定义CollectionJsonSerializer

上面我们偷懒直接复制了jackson的StringSerializer, 对于集合的序列化当然jackson也是提供的
我们可以查看com.fasterxml.jackson.databind.ser.impl.StringCollectionSerializer, StringCollectionSerializer是没有被final修饰的我们可以直接继承,当然也可以不继承

    private class CollectionPrefixJsonSerializer extends JsonSerializer<Collection<String>> {

        @Override
        public void serialize(Collection<String> value, JsonGenerator g, SerializerProvider provider) throws IOException {
            g.setCurrentValue(value);
            int len = value.size();
            g.writeStartArray(len);
            serializeContents(value, g, provider);
            g.writeEndArray();
        }

        private final void serializeContents(Collection<String> value, JsonGenerator g, SerializerProvider provider) throws IOException {
            for (String str : value) {
                if (str == null) {
                    provider.defaultSerializeNull(g);
                } else {
                    g.writeString(prefix + str);
                }
            }
        }
    }

其实序列化还是很简单的,

4.3. 自定义StringArraySerializer

学习 com.fasterxml.jackson.databind.ser.impl.StringArraySerializer 基本上和com.fasterxml.jackson.databind.ser.impl.StringCollectionSerializer一样

// 懒得写了

4.4. 自定义MapJsonSerializer

学习com.fasterxml.jackson.databind.ser.std.MapSerializercom.fasterxml.jackson.databind.ser.impl.MapEntrySerializer这两个类,
阅读源码你会发现需要自定义_keySerializer_valueSerializer

// 不想看源码了,不想写

4.4.1. _keySerializer

// 不想看源码了,不想写

4.4.2. _valueSerializer

// 不想看源码了,不想写

4.5. ValuePrefixJsonSerializerFactory

到这里你就发现了, bean只能指定一个序列化对象, 不能指定多个序列化对象, 一个注解很难满足多个类型序列化, 这个时候我们就需要SerializerFactory来帮助我们选择合适的序列化了, 下面我们就来动手实现一个,
同时优化一下上面的代码,

在定义的@ValuePrefix注册的序列化类是ValuePrefixJsonSerializerFactory,

我们上面自己定义的JsonSerializerFactory对象都定义为ValuePrefixJsonSerializerFactory内部类

ContextualSerializer.createContextual()方法有个很有趣的地方那就是它的返回值居然是JsonSerializer, 所以在获取上下文的时候我们可以返回相应的JsonSerializer

/**
 * @author Galaxy
 */
public class ValuePrefixJsonSerializerFactory extends JsonSerializer<Object> implements ContextualSerializer {

    private String prefix = "";

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        prefix = property.getAnnotation(ValuePrefix.class).prefix();
        JavaType type = property.getType();

        // string类型
        if (type.isTypeOrSubTypeOf(String.class)) {
            return new StringPrefixJsonSerializer();
        }

        // Collection类型
        if (type.isCollectionLikeType()) {
            return new CollectionPrefixJsonSerializer();
        }

        // map类型
        //        if (property.getType().isMapLikeType()) {
        //            return this;
        //        }
        throw new JsonMappingException("不支持的类型,  仅支持 String, Collection");
    }


    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {

    }


    /**
     * --------string类型-------------------------------------------------
     */
   private class StringPrefixJsonSerializer extends JsonSerializer<String> {
        @Override
        public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            if (value == null) {
                serializers.defaultSerializeNull(gen);
            } else {
                gen.writeString(prefix + value);
            }
        }
    }


    /**
     * --------Collection类型-------------------------------------------------
     */
    private class CollectionPrefixJsonSerializer extends JsonSerializer<Collection<String>> {

        @Override
        public void serialize(Collection<String> value, JsonGenerator g, SerializerProvider provider) throws IOException {
            g.setCurrentValue(value);
            int len = value.size();
            g.writeStartArray(len);
            serializeContents(value, g, provider);
            g.writeEndArray();
        }

        private final void serializeContents(Collection<String> value, JsonGenerator g, SerializerProvider provider) throws IOException {
            for (String str : value) {
                if (str == null) {
                    provider.defaultSerializeNull(g);
                } else {
                    g.writeString(prefix + str);
                }
            }
        }
    }
}

到这里我们的序列化工作就完成了, 我就讲讲反序列化吧,

5. 反序列化

序列化和反序列化车操作差不多,大家可以自己看源码操作, 下面直接贴出反序列化的源代码吧

5.1. ValuePrefixJsonDeserializerFactory

/**
 * @author Galaxy
 */
public class ValuePrefixJsonDeserializerFactory extends JsonDeserializer<Object> implements ContextualDeserializer {

    private String prefix = "";

    private Collection collection;


    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) throws JsonMappingException {
        prefix = beanProperty.getAnnotation(ValuePrefix.class).prefix();
        JavaType type = beanProperty.getType();

        // string类型
        if (type.isTypeOrSubTypeOf(String.class)) {
            return new ValuePrefixJsonDeserializer();
        }

        // Collection类型
        if (type.isCollectionLikeType()) {

            if (type.isTypeOrSubTypeOf(List.class)) {
                collection = new ArrayList();
            } else if (type.isTypeOrSubTypeOf(Set.class)) {
                collection = new HashSet();
            } else {
                throw new JsonMappingException("不是 list 或者 set 接口");
            }
            return new ValuePrefixCollectionJsonDeserializer();
        }

        // map类型
        //        if (property.getType().isMapLikeType()) {
        //            return this;
        //        }
        throw new JsonMappingException("不支持的类型,  仅支持 String, Collection");
    }

    @Override
    public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        return null;
    }


    /**
     * string ----------------------------------------
     */
    class ValuePrefixJsonDeserializer extends JsonDeserializer<String> {

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            String text = p.getText();
            if (text != null) {
                return text.replace(prefix, "");
            }
            return text;
        }
    }


    /**
     * Collection  ---------------------------------------
     */
    class ValuePrefixCollectionJsonDeserializer extends JsonDeserializer<Collection<String>> {


        @Override
        public Collection<String> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            JsonNode jsonNode = jp.getCodec().readTree(jp);
            Iterator<JsonNode> elements = jsonNode.elements();
            return deserialize(elements);
        }

        private Collection deserialize(Iterator<JsonNode> elements) {
            while (elements.hasNext()) {
                JsonNode node = elements.next();
                if (node.isNull()) {
                    collection.add(null);
                } else {
                    String text = node.asText().replace(prefix, "");
                    collection.add(text);
                }
            }
            return collection;
        }
    }
}

6. 参考

  • https://yanbin.blog/customize-jackson-annotation-and-disable-specific-annotation/
  • https://blog.csdn.net/liuxiao723846/article/details/42921333
  • https://stackoverflow.com/questions/43214545/customdeserializer-has-no-default-no-arg-constructor
  • https://codeday.me/bug/20190516/1112991.html
  • https://ketao1989.github.io/2015/02/10/Jackson-Deserialize-Java-Object-Implementation/
  • https://github.com/FasterXML/jackson-databind/issues/2324
  • https://stackoverflow.com/questions/8944086/custom-jackson-deserializer-getting-access-to-current-field-class
  • 13
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值