jackson使用

1.1为什么不允许使用fastjson

fastjson在1.2.24及之前版本存在远程代码执行高危安全漏洞,攻击者可以通过此漏洞远程执行恶意代码来入侵服务器,漏洞出现在com\alibaba\fastjson\parser\DefaultJSONParser.java文件中的DefaultJSONParser::parseObject函数,在1.2.24 (即受影响的版本)版本的代码中,加载类名时,用到了一个TypeUtils::loadClass的方法,此方法在com\alibaba\fastjson\util\TypeUtils.java,通过对代码的分析发现,该方法没有对需要加载的类做限制,而是直接加载从而导致非授权的代码执行。

fastjson一直以极快的parse速度著称,但是其爆出过安全漏洞,且对于项目中目前正在使用的json来讲,fastjson的速度提升并不明显,因此在项目中统一使用jackson。

1.2如何预防fastjson的引入与保证jackson引入的版本一致

通过pom的enforcer插件来预防,禁止引入fastjson的包,且保证所有引入的jackson包的版本一致。



    true
    Dependecy Check Failed!
    
        com.alibaba:fastjson
    



    
        com.fasterxml.jackson.*
    

2.注解的使用

2.1@JsonProperty

@JsonProperty("string")
private String string;

用于属性上的注解,在序列化时将该属性的名称转化为注解内自定义的字符串,或在反序列化一段json串时将对应的字段映射到对应的属性上。

使用@JsonProperty的意义在于在序列化或反序列化属性时,属性对应的json字段可能会不按照java的属性命名规范进行命名,如在反序列化一段json串时可能都是按照xx_yy的规则进行命名的,但是在代码中创建对应类的属性名时仍要继续按照java代码的规则进行命名;或在序列化为redis-key时,为了减少字节,只需要用一个保证唯一尽量简短的名称即可,但是在程序中依然要按照命名规范对属性名进行命名以保证代码的可读性。@JsonProperty可以解决序列化后的json串和原有类中的属性名一一对应并在java代码中继续保持属性名按照命名规范进行命名的问题。

2.2@JsonIgnoreProperties(ignoreUnknown = true)

类上的注解,主要作用于将json串反序列化为object的过程。

当解析的json串中含有类里未定义的属性时,加上@JsonIgnoreProperties(ignoreUnkown = true)注解,可以按照类中已存在的属性将json串反序列化为对应的object;若无@JsonIgnoreProperties(ignoreUnkown = true)注解,将含有未知属性的json串反序列化为object时会失败。

2.3@JsonSerialize(@JsonDeserialize)

类上的注解,常用的属性有using = CustomSerializer.class(using = CustomDeserializer.class),CustomSerializer(CustomDeserializer)为自定义的序列化类(反序列化类),自定义的序列化类(反序列化类)需要直接或间接继承StdSerializer(StdDeserializer)或JsonSerializer(JsonDeserializer),重写方法serialize(deserialize)。

对于重写的serialize方法需要利用JsonGenerator生成json,JsonGenerator有多种write方法以支持生成特定的类型的json,比如writeArray。

对于重写的deserialize方法需要利用JsonParser 读取 json。

注:当自定义了序列化类(反序列化类)并在类上加上了@JsonSerialize(@JsonDeserialize)注解后,会按照自定义序列化类(反序列化类)中的规则来进行序列化(反序列化),此时在类中属性上的@JsonProperty注解会失效。

package com.xiaomi.video.k.api.util;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

/**
 * @author qusheng
 */
public class CustomSerializer extends JsonSerializer {
    @Override
    public void serialize(TempResponse tempResponse, JsonGenerator jgen,
                          SerializerProvider provider) throws IOException {
        jgen.writeStartObject();
        jgen.writeNumberField("intValue", tempResponse.getInteger());
        jgen.writeStringField("stringValue", tempResponse.getString());
        jgen.writeEndObject();
    }
}
package com.xiaomi.video.k.api.util;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.IntNode;

import java.io.IOException;

/**
 * @author qusheng
 */
public class CustomDeserializer extends JsonDeserializer {
    @Override
    public TempResponse deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        TempResponse tempResponse = new TempResponse();
        int intValue = (Integer) ((IntNode) node.get("intValue")).numberValue();
        String strValue = node.get("stringValue").asText();
        tempResponse.setInteger(intValue);
        tempResponse.setString(strValue);
        return tempResponse;
    }
}

2.4@JsonInclude

@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class TempResponse
@JsonInclude(value = JsonInclude.Include.NON_EMPTY, content = JsonInclude.Include.NON_EMPTY)
@JsonProperty("map")
private Map map;

既可以在类上使用,也可以在具体的某个属性上使用。注解中有value和content两种属性,value主要作用于属性的值为特定情况时,在序列化后忽略,content主要作用于map类型属性的value为特定情况时,在序列化后忽略。

注:在使用content时,需在对应的map属性上加注解。

下面的表格讨论了map对应的情形下该加怎样的注解以在转化为json串时不显示整个map。

情形 map属性的值 map属性上的注解

map彻底为空

null
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)

map不为空,value有默认值

{"key1": ""}
@JsonInclude(value = JsonInclude.Include.NON_EMPTY, content = JsonInclude.Include.NON_DEFAULT)

map不为空,value有null

{"key1": null}
@JsonInclude(value = JsonInclude.Include.NON_EMPTY, content = JsonInclude.Include.NON_EMPTY)或
@JsonInclude(value = JsonInclude.Include.NON_EMPTY, content = JsonInclude.Include.NON_NULL)
public enum Include{
        ALWAYS,//always include property
        NON_NULL,//do not include property with null value
        NON_ABSENT,//no null values including no content null values like Optional, AtomicReference etc
        NON_EMPTY,//NON_NULL + NON_ABSENT + values like empty Collections/Map/arrays/String etc are excluded
        NON_DEFAULT,//no default values, e.g. no primitives with default values
        ;
    }


2.5@JsonFormat

主要用于Date类型的属性上的注解。

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;

shape用于设置序列化后的类型,详见Shape枚举类。建议使用JsonFormat.Shape.STRING,这样可以将序列化后属性对应的值全部转化为string输出,因为在序列化long型的数据时,如果数字长度过长,会出现丢失精度的问题,所以建议都转换为string来进行api与app之间的交互。同样可以通过重写configureMessageConverters方法来实现。

pattern用于设置序列化后的日期格式,如yyyy-mm-dd HH:mm:ss,此处的pattern应与java.text.SimpleDateFormat中的Time Patterns一致。

timezone用于设置序列化指定时区的时间,如GMT+8代表北京时间所使用的东八区。

Letter  Date or Time Component  Presentation    Examples
G   Era designator  Text    AD
y   Year    Year    1996; 96
Y   Week year   Year    2009; 09
M   Month in year   Month   July; Jul; 07
w   Week in year    Number  27
W   Week in month   Number  2
D   Day in year Number  189
d   Day in month    Number  10
F   Day of week in month    Number  2
E   Day name in week    Text    Tuesday; Tue
u   Day number of week (1 = Monday, ..., 7 = Sunday)    Number  1
a   Am/pm marker    Text    PM
H   Hour in day (0-23)  Number  0
k   Hour in day (1-24)  Number  24
K   Hour in am/pm (0-11)    Number  0
h   Hour in am/pm (1-12)    Number  12
m   Minute in hour  Number  30
s   Second in minute    Number  55
S   Millisecond Number  978
z   Time zone   General time zone   Pacific Standard Time; PST; GMT-08:00
Z   Time zone   RFC 822 time zone   -0800
X   Time zone   ISO 8601 time zone  -08; -0800; -08:00

2.6@JsonPropertyOrder

类上的注解,用于对类中的每个属性按照指定的顺序进行序列化。平时使用起来的机会感觉不多,但是可以了解一下怎么用。

若自定义某几个属性的顺序,则其他的属性按照在类中定义的顺序进行输出序列化后的json串。注:若在属性上有@JsonProperty的注解,在value里应写@JsonProperty中的注解;@JsonPropertyOrder同样可以作用于父类的属性。

@JsonPropertyOrder(value = {"integer", "string"})

也可以设置按照字典序进行排序

@JsonPropertyOrder(alphabetic=true)

3.普通对象、数组、list、带范型对象与json串间的转换

可以通过调用common-lib中JacksonJsonUtil类对应的方法进行序列化与反序列化,注意在需要序列化或反序列化的类的属性上加上需要的注解。

  object->json json->object
普通object
JacksonJsonUtil.toString(Object o)
JacksonJsonUtil.toObject(String jsonStr, Class tClass)
数组
JacksonJsonUtil.toString(List list)
JacksonJsonUtil.ofList(String jsonStr, Class tClass)
带泛型的object
JacksonJsonUtil.toString(Object o)
JacksonJsonUtil.toObject(String jsonStr, TypeReference typeReference

4.jackson在SpringMVC中的应用

在api项目开发过程中,传给前段序列化后的json串需要对null的属性分别进行特殊处理。通过从SerializerFeature枚举类中遍历取出每个属性的ordinal属性,通过对其进行位运算取出一个mask的int值(其实就是2的0-3次方),用一个初始为0的config对每一个属性的mask进行或操作再赋值给config,然后再用config和枚举类中的属性的mask进行与操作,判断结果是否为0,若不为0则将一个重写过serialize方法的新的继承于JsonSerializer的子类赋给对应的属性。再通过在加了@Configuration注解的继承于WebMvcConfigurerAdapter的子类中重写configureMessageConverters方法来实现在controller返回时将返回的对象序列化为我们所期望的json格式。

package com.xiaomi.video.k.api.web.configurer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.nio.charset.Charset;
import java.util.List;

/**
 * Created by tanglilu on 2018/11/13.
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List> converters) {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.getObjectMapper().enable(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS);
        converter.getObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
        converter.getObjectMapper().setSerializerFactory(converter.getObjectMapper().getSerializerFactory().withSerializerModifier(
                new JsonSerializerForJackson(SerializerFeature.values())
        ));
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        converter.setPrettyPrint(true);
        converters.add(new StringHttpMessageConverter());
        converters.add(converter);
    }
}
package com.xiaomi.video.k.api.web.configurer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;

import java.io.IOException;
import java.util.List;

import static com.xiaomi.video.k.api.web.configurer.SerializerFeature.WriteNullBooleanAsFalse;
import static com.xiaomi.video.k.api.web.configurer.SerializerFeature.WriteNullListAsEmpty;
import static com.xiaomi.video.k.api.web.configurer.SerializerFeature.WriteNullNumberAsZero;
import static com.xiaomi.video.k.api.web.configurer.SerializerFeature.WriteNullStringAsEmpty;

/**
 * Created by tanglilu
 */
public class JsonSerializerForJackson extends BeanSerializerModifier {

    private final JsonSerializer nullBooleanJsonSerializer;
    private final JsonSerializer nullNumberJsonSerializer;
    private final JsonSerializer nullListJsonSerializer;
    private final JsonSerializer nullStringJsonSerializer;

    JsonSerializerForJackson(SerializerFeature... features) {
        int config = 0;
        for (SerializerFeature feature : features) {
            config |= feature.mask;
        }
        nullBooleanJsonSerializer = (config & WriteNullBooleanAsFalse.mask) != 0 ? new NullBooleanSerializer() : null;
        nullNumberJsonSerializer = (config & WriteNullNumberAsZero.mask) != 0 ? new NullNumberSerializer() : null;
        nullListJsonSerializer = (config & WriteNullListAsEmpty.mask) != 0 ? new NullListJsonSerializer() : null;
        nullStringJsonSerializer = (config & WriteNullStringAsEmpty.mask) != 0 ? new NullStringSerializer() : null;
    }

    @Override
    public List changeProperties(SerializationConfig config, BeanDescription beanDesc, List beanProperties) {
        for (BeanPropertyWriter writer : beanProperties) {
            final JavaType javaType = writer.getType();
            final Class rawClass = javaType.getRawClass();
            if (javaType.isArrayType() || javaType.isCollectionLikeType()) {
                writer.assignNullSerializer(nullListJsonSerializer);
            } else if (Number.class.isAssignableFrom(rawClass) && rawClass.getName().startsWith("java.lang")) {
                writer.assignNullSerializer(nullNumberJsonSerializer);
            } else if (Boolean.class.equals(rawClass)) {
                writer.assignNullSerializer(nullBooleanJsonSerializer);
            } else if (String.class.equals(rawClass)) {
                writer.assignNullSerializer(nullStringJsonSerializer);
            }
        }
        return beanProperties;
    }

    private static class NullListJsonSerializer extends JsonSerializer {
        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeStartArray();
            jgen.writeEndArray();
        }
    }

    private static class NullNumberSerializer extends JsonSerializer {
        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeNumber(0);
        }
    }

    private static class NullBooleanSerializer extends JsonSerializer {
        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeBoolean(false);
        }
    }

    private static class NullStringSerializer extends JsonSerializer {
        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeString("");
        }
    }
} 
package com.xiaomi.video.k.api.web.configurer;

/**
 * Created by tanglilu
 */
public enum SerializerFeature {
   WriteNullListAsEmpty,
   WriteNullStringAsEmpty,
   WriteNullNumberAsZero,
   WriteNullBooleanAsFalse;

   public final int mask;

   SerializerFeature() {
      mask = (1 << ordinal());
   }

}

5.mapper.writerWithDefaultPrettyPrinter().writeValueAsString()

当在test中测试json的序列化时,建议使用mapper.writerWithDefaultPrettyPrinter().writeValueAsString()将对应的对象序列化为json串,这样序列化后的json串输出时带有对应的缩进与换行会更加便于阅读。

ObjectMapper objectMapper = new ObjectMapper();
        try {
            System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(tempResponse));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }


你可能感兴趣的:(jackson使用)