json序列化

Gson、FastJson【阿里巴巴的开源JSON解析库】、Jackson都是第三方序列化/反序列化 JSON格式的 Java库。JSON 是一种文本形式的数据交换格式,它比XML更轻量、比二进制容易阅读和编写,调式也更加方便。

Jackson是一个简单的基于Java的库,用于将Java对象序列化为JSON,也可以把JSON转换为Java对象,是SpringBoot默认序列化Json库。
SpringBoot提供了 Jackson 的自动配置,spring-boot-starter-json。当自动配置好ObjectMapper后会加载到类路径上。一些配置属性用于自定义 ObjectMapper。

1、Fastjson

Fastjson是阿里巴巴的开源JSON解析库,基于Java语言,支持JSON格式的字符串与JavaBean之间的相互转换。它采用一种“假定有序快速匹配”的算法,把JSON Parse的性能提升到了极致。

关于Fastjson版本至少要在1.2.70以上,为啥?之前版本的漏洞太多。

public static void main(String[] args) {

    String json = "{\"address\":\"Beijing\",\"age\":28,\"user_name\":\"Tom\"}";
    // 将json转换为JavaBean
    User user = JSONObject.parseObject(json, User.class);
    System.out.println(user);

    // 将JavaBean转换为json
    String result = JSONObject.toJSONString(user);
    System.out.println(result);
}

构造json时你是否发现json字符串中有“user_name”这样的格式,FastJson默认会将这种下划线格式的key,与JavaBean中驼峰格式的属性进行绑定。

FastJson的API使用起来也是非常简单,而且它的特点,也就是卖点就是“快”。虽然网上有各种测试,质疑FastJson的“快”,但排除测试者测试用例或环境的影响,整体来看FastJson并不比市面上的其他同类框架慢。

那么什么是AutoType?为什么又会导致漏洞呢?

对于JSON框架Java对象转换成字符串通常可以基于属性或setter/getter方法。FastJson和Jackson是通过遍历出该类中的所有getter方法进行的,Gson是通过反射遍历该类中的所有属性,并把其值序列化成json。。

当一个类中包含了一个接口(或抽象类),在使用FastJson进行序列化的时候,会将子类型抹去,只保留接口(抽象类)的类型,使得反序列化时无法拿到原始类型。

因此,FastJson引入了AutoType,在序列化时把原始类型记录下来。

有了autoType功能,FastJson在对JSON字符串进行反序列化时,会读取@type到内容,试图把JSON内容反序列化成对象,并且会调用它的setter方法。利用这个特性,就可以构造一个JSON字符串,并且使用@type指定一个自己想要使用的攻击类库。

2、Jackson

Jackson是比较主流的基于Java的JSON类库,可用于Json和XML与JavaBean之间的序列化和反序列化。
没看错,Jackson也可以处理JavaBean与XML之间的转换,基于jackson-dataformat-xml组件,而且比较JDK自带XML实现更加高效和安全。而我们使用比较多的是处理JSON与JavaBean之间的功能。

Jackson的核心模块由三部分组成(从Jackson 2.x开始):jackson-core、jackson-annotations、jackson-databind。

  • jackson-core:核心包,定义了低级流(Streaming)API,提供基于"流模式"解析。Jackson内部实现正是通过高性能的流模式API的JsonGenerator和JsonParser来生成和解析json。
  • jackson-annotations,注解(Annotations)包,提供标准的Jackson注解功能。
  • jackson-databind:数据绑定(Databind)包,实现了数据绑定(和对象序列化)支持,它依赖于Streaming和Annotations包。提供基于“对象绑定”解析的API(ObjectMapper)和"树模型"解析的API(JsonNode);基于"对象绑定"解析的API和"树模型"解析的API依赖基于“流模式”解析的API。

jackson-databind依赖于Streaming和Annotations包,因此,引入jackson-databind相当于引入了jackson-core和jackson-annotations。

2.1、Jackson核心类ObjectMapper

Jackson提供了三种JSON的处理方式,分别是:数据绑定、JSON树模型、流式API。其中前两项功能都是基于ObjectMapper来实现的,而流式API功能则需要基于更底层的JsonGenerator和JsonParser来实现。
通常情况下我们使用ObjectMapper类就足够了,它拥有以下功能:

  • 从字符串、流或文件中解析JSON,并创建表示已解析的JSON的Java对象(反序列化)。
  • 将Java对象构建成JSON字符串(序列化)。
  • 将JSON解析为自定义类的对象,也可以解析JSON树模型的对象。

ObjectMapper基于JsonParser和JsonGenerator来实现JSON实际的读/写。这一点看一下ObjectMapper的构造方法即可明白。

2.2、JSON树模型

如果Json字符串比较大,则可使用JSON树模型来灵活的获取所需的字段内容。在Jackson中提供了get、path、has等方法来获取或判断。

@Slf4j
public class JacksonNodeTest {

    /**
     * JavaBean转JSON字符串
     */
    @Test
    public void testJsonNode() {
        // 构建JSON树
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode root = mapper.createObjectNode();
        root.put("id", "zhuan2quan");
        root.put("name", "程序新视界");
        ArrayNode interest = root.putArray("interest");
        interest.add("Java");
        interest.add("Spring Boot");
        interest.add("JVM");

        // JSON树转JSON字符串
        String json = null;
        try {
            json = mapper.writeValueAsString(root);
        } catch (JsonProcessingException e) {
            log.error("Json Node转换异常", e);
        }
        System.out.println(json);
    }

    /**
     * 解析JSON字符串为JSON树模型
     */
    @Test
    public void testJsonToJsonNode() {
        String json = "{\"id\":\"zhuan2quan\",\"name\":\"程序新视界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
        ObjectMapper mapper = new ObjectMapper();
        try {
            // 将JSON字符串转为JSON树
            JsonNode jsonNode = mapper.readTree(json);
            String name = jsonNode.path("name").asText();
            System.out.println(name);

            JsonNode interestNode = jsonNode.get("interest");
            if (interestNode.isArray()){
                for (JsonNode node : interestNode){
                    System.out.println(node.asText());
                }
            }
        } catch (JsonProcessingException e) {
            log.error("Json Node转换异常", e);
        }
    }
}

其中get方法和path功能相似,区别在于如果要读取的key在Json串中不存在时,get方法会null,而path会返回MissingNode实例对象,在链路方法情况下保证不会抛出异常。

除了上述两种形式,还可以基于底层的流式API来进行操作,主要通过JsonGenerator和JsonParser两个API,但操作起来比较复杂,就不再这里演示了。

2.3、注解的使用

上面通过统一配置可对全局格式的序列化和反序列化进行配置,但某些个别的场景下,需要针对具体的字段进行配置,这就需要用注解。比如当Json字符串中的字段与Java对象中的属性不一致时,就需要通过注解来建立它们直接的关系。
@JsonProperty,作用JavaBean字段上,指定一个字段用于JSON映射,默认情况下映射的JSON字段与注解的字段名称相同。可通过value属性指定映射的JSON的字段名称。
@JsonIgnore可用于字段、getter/setter、构造函数参数上,指定字段不参与序列化和反序列化。
@JsonIgnoreProperties作用于类上,序列化时@JsonIgnoreProperties({“prop1”, “prop2”})会忽略pro1和pro2两个属性。反序列化时@JsonIgnoreProperties(ignoreUnknown=true)会忽略类中不存在的字段。
@JsonFormat作用于字段上,通常用来进行格式化操作。

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

如果JavaBean中的时间字段使用的是JDK8新增的时间日期(LocalDate/LocalTime/LocalDateTime)类型的话,需要添加jackson-datatype-jsr310依赖。在讲依赖部分时,SpringBoot默认引入的依赖中就有这个。
当然,还有一些其他的注解,比如@JsonPropertyOrder、@JsonRootName、@JsonAnySetter、@JsonAnyGetter、@JsonNaming等,当使用时参考对应的文档和示例看一下就可以,这里就不再一一列出了。

2.4、自定义解析器

如果上面的注解和统一配置还无法满足需求,可自定义解析器,示例如下:

public class MyFastjsonDeserialize extends JsonDeserializer<Point> {

    @Override
    public Point deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        Iterator<JsonNode> iterator = node.get("coordinates").elements();
        List<Double> list = new ArrayList<>();
        while (iterator.hasNext()) {
            list.add(iterator.next().asDouble());
        }
        return new Point(list.get(0), list.get(1));
    }
}

定义完成之后,注册到Mapper中:

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Point.class, new MyFastjsonDeserialize());
objectMapper.registerModule(module);

2.5、Spring Boot中的集成

在最开始的时候,我们已经看到Spring Boot默认引入了Jackson的依赖,而且也用我们做什么额外的操作,其实已经在使用Jackson进行Json格式的数据与MVC中参数进行绑定操作了。
如果Spring Boot默认的配置并不适合项目需求,也可以通过内置的配置进行配置,以application.yml配置为例,可通过指定以下属性进行相应选项的配置:

#指定日期格式,比如yyyy-MM-dd HH:mm:ss,或者具体的格式化类的全限定名
spring.jackson.date-format 
#是否开启Jackson的反序列化
spring.jackson.deserialization
#是否开启json的generators.
spring.jackson.generator
#指定Joda date/time的格式,比如yyyy-MM-ddHH:mm:ss). 如果没有配置的话,dateformat会作为backup
spring.jackson.joda-date-time-format
#指定json使用的Locale.
spring.jackson.locale
#是否开启Jackson通用的特性.
spring.jackson.mapper
#是否开启jackson的parser特性.
spring.jackson.parser
#指定PropertyNamingStrategy(CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)或者指定PropertyNamingStrategy子类的全限定类名.
spring.jackson.property-naming-strategy
#是否开启jackson的序列化.
spring.jackson.serialization
#指定序列化时属性的inclusion方式,具体查看JsonInclude.Include枚举.
spring.jackson.serialization-inclusion
#指定日期格式化时区,比如America/Los_Angeles或者GMT+10.
spring.jackson.time-zone

Spring Boot自动配置非常方便,但某些时候需要我们手动配置Bean来替代自动配置的Bean。可通过如下形式进行配置:

@Configuration
public class JacksonConfig {
    @Bean
    @Qualifier("json")
    public ObjectMapper jsonMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper mapper = builder.createXmlMapper(false)
                .build();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }
}
@Resource
private ObjectMapper jsonMapper;

参考文章

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值