java序列化之了解Jackson

695次阅读  |  发布于10月以前

当涉及到在Java中进行JSON序列化和反序列化时,Jackson和Gson是两个最常用的库。它们都提供了强大的功能来处理JSON数据,但在某些方面有一些不同之处。

Jackson

Jackson 是一个功能强大且灵活的 JSON 处理库,由 FasterXML 维护。以下是 Jackson 的一些特点

强大的功能

Jackson 提供了广泛的功能,包括 JSON 到 Java 对象的转换,Java 对象到 JSON 的转换,以及 JSON 树模型的处理。

JSON 和 Java 对象互相转换的例子

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        // 创建ObjectMapper实例
        ObjectMapper objectMapper = new ObjectMapper();

        // 将JSON字符串转换为Java对象
        String json = "{\"name\":\"John\",\"age\":30,\"email\":\"john@example.com\"}";
        User user = objectMapper.readValue(json, User.class);
        System.out.println("Java对象: " + user);

        // 将Java对象转换为JSON字符串
        User newUser = new User("Alice", 25, "alice@example.com");
        String jsonString = objectMapper.writeValueAsString(newUser);
        System.out.println("JSON字符串: " + jsonString);
    }
}

class User {
    private String name;
    private int age;
    private String email;

    // 省略构造函数、getter和setter

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                '}';
    }
}

Json树模型

JSON树模型是指将 JSON 数据表示为树形结构的一种模型。在Java中,使用Jackson或者其他JSON处理库解析JSON数据时,通常会将JSON数据解析为一个树形结构,这个结构由节点组成,每个节点代表JSON数据的一个部分。JSON树模型中的每个节点可以是以下几种类型之一

对象节点(ObjectNode)

表示JSON对象,包含多个键值对。

数组节点(ArrayNode)

表示JSON数组,包含多个元素。

文本节点(TextNode)

表示JSON中的字符串。

数字节点(NumericNode)

表示JSON中的数值。

布尔节点(BooleanNode)

表示JSON中的布尔值。

空节点(NullNode)

表示JSON中的null值。

通过JSON树模型,我们可以轻松地遍历、访问和修改JSON数据。JSON树模型与JSONPath确实有些相似,它们都提供了一种方便的方式来处理JSON数据,但也有一些区别

虽然它们有些相似,但JSON树模型更注重于表示整个JSON数据的结构,而JSONPath更注重于定位和查询JSON数据的特定部分。

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        // 创建ObjectMapper实例
        ObjectMapper objectMapper = new ObjectMapper();

        // 创建JSON字符串
        String json = "{\"name\":\"John\",\"age\":30,\"email\":\"john@example.com\"}";

        // 解析JSON字符串为JsonNode对象(JSON树模型)
        JsonNode jsonNode = objectMapper.readTree(json);

        // 从JsonNode对象中获取值
        String name = jsonNode.get("name").asText();
        int age = jsonNode.get("age").asInt();
        String email = jsonNode.get("email").asText();

        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
        System.out.println("Email: " + email);
    }
}

灵活性

Jackson 提供了多种不同的方式来定义和定制 JSON 序列化和反序列化的行为,包括使用注解、Mix-in Annotations、自定义序列化器和反序列化器等。

Mix-in Annotations是什么

Mix-in Annotations 是 Jackson 框架提供的一种高级定制技术,允许将注解绑定到 Java 类上,以指示 Jackson 在序列化或反序列化该类时应该应用这些注解。这个技术的主要优点是可以在无法修改源代码的情况下,对已有的 Java 类进行定制,以满足特定的序列化或反序列化需求。

具体来说,Mix-in Annotations 允许你创建一个独立的辅助类(通常是一个接口),在这个辅助类中定义你希望应用到目标类上的注解,然后通过 Jackson 的 ObjectMapper 将这些注解与目标类进行关联。

使用 Mix-in Annotations 的一般步骤如下:

  1. 创建一个辅助类,其中包含你希望应用到目标类上的注解。这个辅助类通常不会包含任何实现代码,只是用来存放注解。
  2. 使用 ObjectMapperaddMixInAnnotations() 方法,将目标类和辅助类进行关联。

下面是一个简单的示例,演示了如何使用 Mix-in Annotations 来定制 JSON 序列化和反序列化行为:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        // 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();

        // 创建 Mix-in Annotations 辅助类
        abstract class MixIn {
            @JsonInclude(JsonInclude.Include.NON_NULL)
            abstract String getEmail();
        }

        // 将 Mix-in Annotations 和目标类关联
        objectMapper.addMixIn(User.class, MixIn.class);

        // 将 Java 对象转换为 JSON 字符串
        User user = new User("John", 30, null);
        String jsonString = objectMapper.writeValueAsString(user);
        System.out.println(jsonString); // 输出: {"name":"John","age":30}
    }
}

class User {
    private String name;
    private int age;
    private String email;

    // 省略构造函数、getter和setter
}

在上面的示例中,我们创建了一个名为 MixIn 的辅助类,在其中定义了一个带有 @JsonInclude 注解的抽象方法。然后,通过调用 ObjectMapperaddMixInAnnotations() 方法,将 MixIn 类与 User 类进行关联,以告诉 Jackson 在序列化 User 类时应该应用 MixIn 类中定义的注解。

性能优化

Jackson 在性能上表现优异,通过使用流式 API、懒加载等技术来减少内存和CPU的开销,并提供了异步处理和基于缓存的优化。

异步处理和基于缓存的优化

在Jackson中,异步处理和基于缓存的优化是通过一些特定的设置和技术来实现的,主要是针对Jackson的ObjectMapper进行配置。

异步处理

异步处理在Jackson中主要是指在处理JSON数据时可以采用异步的方式,以提高性能和效率。异步处理通常涉及到IO操作,例如读取或写入JSON数据到文件、网络或其他数据源。在Jackson中实现异步处理的关键是使用异步的IO流和回调机制。对于读取JSON数据,我们可以使用JsonParser的异步API进行操作,而对于写入JSON数据,可以使用JsonGenerator的异步API进行操作。示例代码如下

ObjectMapper objectMapper = new ObjectMapper();
try (InputStream inputStream = new FileInputStream("data.json")) {
    // 创建JsonParser
    JsonParser jsonParser = objectMapper.getFactory().createNonBlockingByteArrayParser();
    // 设置JsonParser的输入流
    jsonParser.setNonBlockingInput(inputStream);
    // 通过回调处理JSON事件
    jsonParser.setNonBlockingParsing(true);
    jsonParser.nextToken();
    while (jsonParser.nextToken() != null) {
        // 处理JSON事件
    }
}

基于缓存的优化

基于缓存的优化主要是指将Jackson的一些中间结果缓存起来,以提高后续操作的性能和效率。这些中间结果可以是反序列化过程中的解析结果、序列化过程中的JSON片段等。在Jackson中实现基于缓存的优化通常是通过ObjectMapper的一些配置选项来实现的。例如,可以使用ObjectReaderObjectWriter来重用已经配置好的ObjectMapper实例,从而避免重复创建ObjectMapper实例。另外,Jackson还提供了一些缓存相关的配置选项,例如可以通过JsonFactory_createUTF8JsonGenerator方法来创建一个带有缓冲的JsonGenerator,以提高序列化性能。示例代码如下

ObjectMapper objectMapper = new ObjectMapper();
// 重用已经配置好的ObjectMapper实例
ObjectReader reader = objectMapper.reader();
ObjectWriter writer = objectMapper.writer();
// 创建带有缓冲的JsonGenerator
JsonFactory factory = objectMapper.getFactory();
factory._createUTF8JsonGenerator(System.out, JsonGenerator.Feature.AUTO_CLOSE_TARGET);

通过合理配置Jackson的ObjectMapper和相关组件,可以实现异步处理和基于缓存的优化,从而提高JSON数据的处理性能和效率。

流式 API

Jackson 提供了流式 API,用于在处理大型JSON文档时提供更高效和更灵活的方式。流式 API 允许我们以流的形式逐个处理JSON文档的各个部分,而不需要将整个文档加载到内存中。

以下是 Jackson 流式 API 的一些主要特点和用法

JsonParser 和 JsonGenerator

Jackson 的流式 API 主要围绕着 JsonParserJsonGenerator 这两个核心类展开。JsonParser 用于从 JSON 输入流中读取数据,而 JsonGenerator 用于将数据写入到 JSON 输出流中。

逐个读取和写入

使用 JsonParser 可以逐个读取 JSON 文档的各个部分(如对象、数组、字段),而使用 JsonGenerator 可以逐个写入 JSON 文档的各个部分。这种逐个处理的方式使得在处理大型JSON文档时可以更加高效和灵活。

事件模型

Jackson 的流式 API 采用了一种事件驱动的模型,即在读取或写入 JSON 文档时会产生各种事件(如开始对象、结束对象、开始数组、结束数组、字段等),开发者可以根据这些事件来决定如何处理 JSON 数据。

读取JSON文档:

ObjectMapper objectMapper = new ObjectMapper();
try (InputStream inputStream = new FileInputStream("data.json")) {
    JsonParser jsonParser = objectMapper.getFactory().createParser(inputStream);
    while (jsonParser.nextToken() != null) {
        // 处理JSON事件
    }
}

写入JSON文档:

ObjectMapper objectMapper = new ObjectMapper();
try (OutputStream outputStream = new FileOutputStream("output.json")) {
    JsonGenerator jsonGenerator = objectMapper.getFactory().createGenerator(outputStream);
    jsonGenerator.writeStartObject();
    jsonGenerator.writeStringField("name", "John");
    jsonGenerator.writeNumberField("age", 30);
    jsonGenerator.writeEndObject();
}

通过使用 Jackson 的流式 API,我们可以有效地处理大型JSON文档,逐个读取或写入其各个部分,并在处理过程中灵活地控制和处理JSON数据。

广泛的支持

Jackson 支持标准的 JSON 规范,并且与许多其他第三方库和框架集成得很好,比如 Spring、JAX-RS、Hibernate 等。

使用注解的方式定制Json序列化和反序列化的行为

在Jackson中,可以使用各种注解来定制JSON序列化和反序列化的行为。以下是一些常用的注解及其用法

JSON序列化注解

@JsonProperty

用于指定JSON属性名称,可以将Java属性与JSON字段进行映射。


public class User {
    @JsonProperty("username")
    private String name;
    // 省略其他属性和方法
}

@JsonFormat

用于指定日期和时间的格式化方式。

public class Event {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date timestamp;
    // 省略其他属性和方法
}

@JsonInclude

控制在序列化时是否包括某些属性。

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
    // 省略其他属性和方法
}

@JsonRawValue

将属性的原始值直接包含在生成的JSON中,而不是将其序列化为JSON对象或数组。

public class Data {
    @JsonRawValue
    private String rawData;
    // 省略其他属性和方法
}

JSON反序列化注解

@JsonIgnore

忽略某个属性,使其在反序列化时不被处理。


public class User {
    @JsonIgnore
    private String password;
    // 省略其他属性和方法
}

@JsonCreator

用于指定一个静态工厂方法或构造函数,用于从JSON中创建对象。

public class User {
    private String name;
    @JsonCreator
    public User(@JsonProperty("username") String name) {
        this.name = name;
    }
    // 省略其他属性和方法
}

@JsonSetter

用于指定一个方法,用于在反序列化时设置属性值。

public class User {
    private String name;
    @JsonSetter("username")
    public void setName(String name) {
        this.name = name;
    }
    // 省略其他属性和方法
}

@JsonDeserialize

用于指定一个自定义的反序列化器。

public class User {
    @JsonDeserialize(using = CustomDateDeserializer.class)
    private Date birthday;
    // 省略其他属性和方法
}

通过使用这些注解,可以灵活地定制JSON序列化和反序列化的行为,以满足不同的需求和场景。

如何自定义序列化器和反序列化器

在Jackson中,可以通过自定义序列化器(Serializer)和反序列化器(Deserializer)来实现对特定类型的定制化序列化和反序列化行为。这种方式可以让我们完全控制JSON数据的生成和解析过程,以满足特定的需求和场景。

自定义序列化器

自定义序列化器是通过继承JsonSerializer<T>类并重写serialize()方法来实现的。在serialize()方法中,我们可以通过JsonGenerator将Java对象序列化为JSON数据。

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CustomDateSerializer extends JsonSerializer<Date> {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Override
    public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        String formattedDate = dateFormat.format(value);
        gen.writeString(formattedDate);
    }
}

自定义反序列化器

自定义反序列化器是通过继承JsonDeserializer<T>类并重写deserialize()方法来实现的。在deserialize()方法中,我们可以通过JsonParser从JSON数据解析出Java对象。

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CustomDateDeserializer extends JsonDeserializer<Date> {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    @Override
    public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String dateString = p.getText();
        try {
            return dateFormat.parse(dateString);
        } catch (ParseException e) {
            throw new IOException("Error parsing date", e);
        }
    }
}

注册自定义序列化器和反序列化器

完成自定义序列化器和反序列化器的编写后,我们需要将它们注册到ObjectMapper中,以便在序列化和反序列化过程中使用。示例代码

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Date.class, new CustomDateSerializer());
module.addDeserializer(Date.class, new CustomDateDeserializer());
objectMapper.registerModule(module);

通过以上步骤,我们可以自定义序列化器和反序列化器,并将它们注册到Jackson的ObjectMapper中,从而实现对特定类型的定制化序列化和反序列化行为。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8