• 轻松上手Jackjson(珍藏版)


    写在前面

    虽然现在市面上有很多优秀的json解析库,但 Spring默认采用Jackson解析Json。

    本文将通过一系列通俗易懂的代码示例,带你逐步掌握 Jackson 的基础用法、进阶技巧以及在实际项目中的应用场景。

    一、Jackjson简介

    Jackson 是当前用的比较广泛的,用来序列化和反序列化 json 的 Java 的开源框架。

    什么是序列化和反序列化呢?

    • 序列化:将 Java Bean 转换为JSON 字符串
    • 反序列化:将JSON字符串转换为JavaBeen对象

    GitHub地址:https://github.com/FasterXML/jackson

    从GitHub 看到,目前有8.8k stars,最近更新时间是2个月前。可见其更新速度还是比较活跃的

    也是json最流行的解析器之一

    二、Jackjson优点

    • Jackson 所依赖的 jar 包较少,简单易用
    • 其他 Java 的 json 的框架 Gson 等相比, Jackson 解析大的 json 文件速度比较快
    • Jackson 运行时占用内存比较低,性能比较好
    • Jackson 有灵活的 API,可以很容易进行扩展和定制

    三、Jackjson模块及依赖

    ① Jackson库包括三个主要的模块

    • jackson-databind用于数据绑定
    • jackson-core用于JSON解析和生成
    • jackson-annotations用于注解支持

    ② 所需依赖

    jackson-databind 依赖 jackson-core 和 jackson-annotations,所以可以只显示地添加jackson-databind依赖,jackson-core 和 jackson-annotations 也随之添加到 Java 项目工程中

    maven

    <dependency>
    <groupId>com.fasterxml.jackson.coregroupId>
    <artifactId>jackson-coreartifactId>
    <version>2.15.3version>
    dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.coregroupId>
    <artifactId>jackson-annotationsartifactId>
    <version>2.15.2version>
    dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.coregroupId>
    <artifactId>jackson-databindartifactId>
    <version>2.15.2version>
    dependency>

    Gradle

    dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4'
    }

    四、快速上手

    假设我们有一个简单的 User 类:

    @Data
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    private String name;
    private int age;
    }

    4.1 Java对象转JSON字符串

    @Test
    public void testSerializationExample() throws JsonProcessingException {
    User user = new User("小凡", 18);
    ObjectMapper objectMapper = new ObjectMapper();
    String userstr = objectMapper.writeValueAsString(user);
    System.out.println(userstr);
    }
    //输出
    {"name":"小凡","age":18}

    4.2 JSON字符串转Java对象

    @Test
    public void testDeserializationExample() throws JsonProcessingException {
    String json ="{\"name\":\"小凡\",\"age\":18}";
    ObjectMapper objectMapper = new ObjectMapper();
    User user = objectMapper.readValue(json, User.class);
    System.out.println(user);
    }
    //输出
    User(name=小凡, age=18)

    上面例子中我们我们使用了ObjectMapper 它是 Jackson 库的核心类,负责实现 Java 对象与 JSON 文本之间的相互转换

    五、Jackjson序列化API

    5.1 普通Java对象序列化

    @Test
    public void testObjectToJson() throws JsonProcessingException {
    User user = new User();
    user.setName("小凡");
    user.setAge(18);
    ObjectMapper mapper = new ObjectMapper();
    String userstr = mapper.writeValueAsString(user);
    System.out.println(userstr);
    }
    //输出
    {"name":"小凡","age":18}

    5.2 复杂对象序列化

    ① 构造作者出版信息对象

    //书籍对象
    @Data
    public class Book {
    private String bookName;
    private String publishDate;
    private String publishHouse;
    private Double price;
    }
    //地址对象
    @Data
    public class Address {
    private String city;
    private String street;
    }
    //作者出版信息对象
    @Data
    public class PublishInfo {
    private String name;
    private String sex;
    private Integer age;
    private Address addr;
    private List books;
    }

    ② 复杂对象转JSON字符串

    @Test
    public void testComplexObjectToJson() throws JsonProcessingException {
    //构造所有出版的书籍list
    ArrayList books = new ArrayList();
    Book book1 = new Book();
    book1.setBookName("Java从入门到放弃");
    book1.setPublishDate("2004-01-01");
    book1.setPublishHouse("小凡出版社");
    book1.setPrice(66.66);
    Book book2 = new Book();
    book2.setBookName("Spring从入门到入土");
    book2.setPublishDate("2024-01-01");
    book2.setPublishHouse("小凡出版社");
    book2.setPrice(88.88);
    books.add(book1);
    books.add(book2);
    //构造作者地址信息
    Address addr = new Address();
    addr.setCity("昆明");
    addr.setStreet("xxx区xxx路xxx号");
    //构造作者出版的所有书籍信息
    PublishInfo publishInfo = new PublishInfo();
    publishInfo.setName("小凡");
    publishInfo.setSex("男");
    publishInfo.setAge(18);
    publishInfo.setAddr(addr);
    publishInfo.setBooks(books);
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(publishInfo);
    System.out.println(json);
    }
    //返回
    {"name":"小凡","sex":"男","age":18,"addr":{"city":"昆明","street":"xxx区xxx路xxx号"},"books":[{"bookName":"Java从入门到放弃","publishDate":"2004-01-01","publishHouse":"小凡出版社","price":66.66},{"bookName":"Spring从入门到入土","publishDate":"2024-01-01","publishHouse":"小凡出版社","price":88.88}]}

    5.3 List集合序列化

    @Test
    public void testListToJson() throws JsonProcessingException {
    User user1 = new User();
    user1.setName("小凡001");
    user1.setAge(18);
    User user2 = new User();
    user2.setName("小凡002");
    user2.setAge(30);
    ArrayList users = new ArrayList<>();
    users.add(user1);
    users.add(user2);
    ObjectMapper mapper = new ObjectMapper();
    String userstr = mapper.writeValueAsString(users);
    System.out.println(userstr);
    }
    //输出
    [{"name":"小凡001","age":18},{"name":"小凡002","age":30}]

    5.4 Map集合序列化

    @Test
    public void testMapToJson() throws JsonProcessingException {
    User user = new User();
    user.setName("小凡");
    user.setAge(18);
    List asList = Arrays.asList("抽烟", "喝酒", "烫头发");
    HashMap map = new HashMap<>();
    map.put("user", user);
    map.put("hobby",asList);
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(map);
    System.out.println(json);
    }
    //输出
    {"user":{"name":"小凡","age":18},"hobby":["抽烟","喝酒","烫头发"]}

    @Test
    public void testMapToJsonSup() throws JsonProcessingException {
    User user1 = new User();
    user1.setName("小凡001");
    user1.setAge(18);
    User user2 = new User();
    user2.setName("小凡002");
    user2.setAge(30);
    ArrayList users = new ArrayList<>();
    users.add(user1);
    users.add(user2);
    List asList = Arrays.asList("抽烟", "喝酒", "烫头发");
    HashMap map = new HashMap<>();
    map.put("users", users);
    map.put("hobby",asList);
    map.put("name","张三");
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(map);
    System.out.println(json);
    }
    //输出
    {"name":"张三","users":[{"name":"小凡001","age":18},{"name":"小凡002","age":30}],"hobby":["抽烟","喝酒","烫头发"]}

    5.5 日期处理

    默认情况下,jackjson会将日期类型属性序列化成long型值(自1970年1月1日以来的毫秒数)。显然这样格式的数据不符合人类直观查看

    假设我们有个Person对象

    @Data
    public class Person {
    private String name;
    private Date birthday;
    }

    ① 我们先来看看默认转换的结果

    @Test
    public void testDateToJsonDefault() throws JsonProcessingException {
    Person person = new Person();
    person.setName("小凡");
    person.setBirthday(new Date());
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(person);
    System.out.println(json);
    }
    //输出
    {"name":"小凡","birthday":1712220896407}

    ② 通过SimpleDateFormat 将日期格式化成人类可看格式显示

    @Test
    public void testDateToJson() throws JsonProcessingException {
    Person person = new Person();
    person.setName("小凡");
    person.setBirthday(new Date());
    ObjectMapper mapper = new ObjectMapper();
    //进行一下日期转换
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    mapper.setDateFormat(dateFormat);
    String json = mapper.writeValueAsString(person);
    System.out.println(json);
    }
    //输出 这个格式就人性化多了
    {"name":"小凡","birthday":"2024-04-04"}

    六、Jackjson反序列化API

    Jackson通过将JSON字段的名称与Java对象中的getter和setter方法进行匹配,将JSON对象的字段映射到Java对象中的属性。

    Jackson删除了getter和setter方法名称的“ get”和“ set”部分,并将其余名称的第一个字符转换为小写。

    6.1 普通JSON字符串反序列化

    6.1.1 JSON字符串->Java对象

    注: 这里我们还是使用前面小节中创建的User 实体类

    @Test
    public void testStrToObject() throws JsonProcessingException {
    String json ="{\"name\":\"小凡\",\"age\":18}";
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(json, User.class);
    System.out.println(user);
    }
    //输出
    User(name=小凡, age=18)
    6.1.2 字符输入流-->Java对象
    @Test
    public void testReaderToObject() throws JsonProcessingException {
    String json ="{\"name\":\"小医仙\",\"age\":18}";
    Reader reader = new StringReader(json);
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(json, User.class);
    System.out.println(user);
    }
    //输出
    User(name=小医仙, age=18)
    6.1.3 字节输入流->Java对象

    ① 创建user001.json文件,文件内容如下

    image-20240404204833787

    ②将字节输入流转换为User对象

    @Test
    public void testInputStreamToObject() throws IOException {
    FileInputStream inputStream = new FileInputStream("F:\\vueworkspace\\jackjson-demo\\jackjson-demo\\src\\json\\user001.json");
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(inputStream, User.class);
    System.out.println(user);
    }
    }
    //输出
    User(name=萧炎, age=18)
    6.1.4 JSON 文件反->Java对象

    ①我们准备一个user.json文件,内容如下

    image-20240404173608592

    ② 读取user.json文件中内容,并转换成User 对象

    @Test
    public void testJsonfileToObject() throws IOException {
    File file = new File("F:\\vueworkspace\\jackjson-demo\\jackjson-demo\\src\\json\\user.json");
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(file, User.class);
    System.out.println(user);
    }
    //输出
    User(name=萧炎, age=18)
    6.1.5 URL文件->Java对象

    ① 我们在网络上放一个user.json资源文件,内容如下

    {"name":"紫妍","age":18}

    ② 通过URL(java.net.URL) 将JSON转换成User对象

    @Test
    public void testUrlToObject() throws IOException {
    String url ="https://files.cnblogs.com/files/blogs/685650/user.json";
    URL url1 = new URL(url);
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(url1, User.class);
    System.out.println(user);
    }
    //输出
    User(name=紫妍, age=18)
    6.1.6 字节数组-> java对象
    @Test
    public void testByteToObject() throws IOException {
    String json ="{\"name\":\"韩雪\",\"age\":18}";
    byte[] bytes = json.getBytes();
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(bytes, User.class);
    System.out.println(user);
    }
    //输出
    User(name=韩雪, age=18)

    6.2 JSON数组字符串 反序列化

    6.2.1 JSON字符串->Map集合
    @Test
    public void testJsonStrToMap() throws JsonProcessingException {
    //{"name":"小凡","sex":"男","age":18}
    String json ="{\"name\":\"小凡\",\"sex\":\"男\",\"age\":18}";
    ObjectMapper mapper = new ObjectMapper();
    Map map = mapper.readValue(json, new TypeReference>() {});
    System.out.println(map);
    }
    //输出
    {name=小凡, sex=男, age=18}
    6.2.2 JSON数组字符串->List集合
    @Test
    public void testJsonArrToList() throws JsonProcessingException {
    //[{"name":"小凡001","age":18},{"name":"小凡002","age":30}]
    String json ="[{\"name\":\"小凡001\",\"age\":18},{\"name\":\"小凡002\",\"age\":30}]";
    ObjectMapper mapper = new ObjectMapper();
    List users = mapper.readValue(json, new TypeReference>() {
    });
    System.out.println(users);
    }
    //输出
    [User(name=小凡001, age=18), User(name=小凡002, age=30)]
    6.2.3 JSON数组字符串->Java对象数据
    @Test
    public void testJsonArrToObjArr() throws JsonProcessingException {
    //[{"name":"小凡001","age":18},{"name":"小凡002","age":30}]
    String json ="[{\"name\":\"小凡001\",\"age\":18},{\"name\":\"小凡002\",\"age\":30}]";
    ObjectMapper mapper = new ObjectMapper();
    User[] users = mapper.readValue(json, User[].class);
    for (User user : users) {
    System.out.println(user);
    }
    }
    //输出
    User(name=小凡001, age=18)
    User(name=小凡002, age=30)

    七、自定义序列化反序列化

    通过自定义序列化和反序列化可以使其更加灵活多变

    7.1 自定义序列化

    有时候,我们不需要jackjson默认的序列化方式。例如,JSON中使用与Java对象中不同的属性名称,

    或者不需要Java对象中的某个字段,这时我们就需要自定义序列化器

    我们先来看看User实体对象的定义如下

    @Data
    public class User {
    private String name;
    private Integer age;
    }

    按照下面默认序列化代码,我们最后得到的JSON字符串如下

    @Test
    public void testSerializationExample() throws JsonProcessingException {
    User user = new User("小凡", 18);
    ObjectMapper objectMapper = new ObjectMapper();
    String userstr = objectMapper.writeValueAsString(user);
    System.out.println(userstr);
    }
    //输出
    {"name":"小凡","age":18}

    而现在的需求升级了,再不创建或修改Users实体对象的情况下,我们想要得到{"username":"小凡","userage":18} 这样的字符串,

    应该怎么办呢?

    这时,我们就需要自定义序列化器,就可以轻松实现,具体代码如下

    @Test
    public void testDefineSerialize() throws JsonProcessingException {
    SimpleModule version1Module = new SimpleModule();
    version1Module.addSerializer(User.class, new JsonSerializer() {
    @Override
    public void serialize(User user, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    jsonGenerator.writeStartObject();
    jsonGenerator.writeStringField("username", user.getName());
    jsonGenerator.writeNumberField("userage", user.getAge());
    jsonGenerator.writeEndObject();
    }
    });
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(version1Module);
    User user = new User("小凡", 18);
    String json = objectMapper.writeValueAsString(user); // 使用版本1的序列化器
    System.out.println( json);
    }
    //输出
    {"username":"小凡","userage":18}

    7.2 自定义反序列化器

    同理,反序列化也可以自定义,具体代码如下

    ① 自定义一个反序列化器

    public class UserDeserializer extends StdDeserializer {
    public UserDeserializer() {
    this(null);
    }
    public UserDeserializer(Class vc) {
    super(vc);
    }
    @Override
    public User deserialize(JsonParser jp, DeserializationContext ctxt)
    throws IOException {
    JsonNode node = jp.getCodec().readTree(jp);
    String name = node.get("name").asText();
    int age = (Integer) ((IntNode) node.get("age")).numberValue();
    return new User(name, age);
    }
    }

    ②将JSON字符串反序列化为 User 对象

    @Test
    public void testUserDeserializer() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addDeserializer(User.class, new UserDeserializer());
    objectMapper.registerModule(module);
    String json = "{\"name\":\"小凡\",\"age\":30}";
    User user = objectMapper.readValue(json, User.class);
    System.out.println(user);
    }
    //输出
    User(name=小凡, age=30)

    八、树模型

    Jackson具有内置的树模型,可用于表示JSON对象。Jackson树模型由JsonNode类表示。

    在处理JSON时,我们有时并不直接关心或无法直接映射到特定的Java对象,而是需要对JSON内容进行动态、灵活的操作。

    这时,树模型就派上了用场。我们可以遍历、查询、更新或合并JSON数据,而无需预先定义对应的Java类

    8.1 JsonNode类型概览

    JsonNode家族包括以下主要子类型:

    • ObjectNode:对应JSON对象,包含一组键值对,可以通过.put(key, value)添加或更新属性。
    • ArrayNode:对应JSON数组,包含一系列元素,可通过.add(value)插入新元素。
    • TextNodeIntNodeLongNodeDoubleNode等:分别对应JSON中的字符串、整数、长整数、浮点数等基本类型值。
    • BooleanNodeNullNode:分别对应JSON中的布尔值和null值。

    8.2 创建与操作JsonNode实例

    下面通过一些具体的代码示例,展示如何使用JackJson的树模型API来创建、操作JsonNode对象。

    示例1:创建简单JsonNode

    @Test
    public void testJackJsonTreeModelExample(){
    // 创建 ObjectMapper 实例
    ObjectMapper mapper = new ObjectMapper();
    // 创建并初始化各类型 JsonNode 实例
    ObjectNode personNode = mapper.createObjectNode();
    personNode.put("name", "小凡");
    personNode.put("age", 18);
    ArrayNode hobbiesNode = mapper.createArrayNode();
    hobbiesNode.add("写代码").add("看书").add("打豆豆");
    personNode.set("hobbies", hobbiesNode);
    // 输出构建的 JSON 字符串
    System.out.println(personNode.toString());
    }
    //输出
    {"name":"小凡","age":18,"hobbies":["写代码","看书","打豆豆"]}

    上述代码首先创建了一个ObjectMapper实例,它是JackJson的核心工具类,负责序列化和反序列化工作。接着,我们创建了一个ObjectNode代表JSON对象,设置了"name"和"age"两个属性。然后创建了一个ArrayNode存储爱好列表,并将其添加到personNode中。最后,打印输出构建的JSON字符串。

    示例2:从JSON字符串反序列化为JsonNode

    @Test
    public void testJackJsonTreeModelExample2() throws JsonProcessingException {
    String jsonInput = "{\"name\":\"小凡\",\"age\":18,\"hobbies\":[\"写代码\",\"看书\",\"打豆豆\"]}";
    ObjectMapper mapper = new ObjectMapper();
    JsonNode rootNode = mapper.readTree(jsonInput);
    System.out.println(rootNode.get("name").asText());
    System.out.println(rootNode.get("age").asInt());
    System.out.println(rootNode.get("hobbies").get(0).asText());
    System.out.println(rootNode.get("hobbies").get(1).asText());
    System.out.println(rootNode.get("hobbies").get(2).asText());
    }
    //输出
    小凡
    18
    写代码
    看书
    打豆豆

    在此示例中,我们首先定义了一个JSON字符串。然后使用ObjectMapperreadTree()方法将其反序列化为JsonNode。接下来,通过调用.get(key)方法访问对象属性或数组元素,并使用.asText().asInt()等方法获取其值。

    8.3 利用JackJson树模型进行深度查询、修改、合并等操作

    上面给出的两个案例属于入门级操作,我们学会后可以接着进行一些高级操作

    @Test
    public void testJsonManipulationExample() throws JsonProcessingException {
    String JSON_STRING = "{\"name\":\"小凡\",\"age\":18,\"hobbies\":[\"写代码\",\"看书\",\"打豆豆\"]}";
    ObjectMapper mapper = new ObjectMapper();
    // 将JSON字符串转换为JsonNode对象
    JsonNode rootNode = mapper.readTree(JSON_STRING);
    // **深度查询**
    // 查询"年龄"
    int age = rootNode.get("age").asInt();
    System.out.println("Age: " + age);
    // 查询第一个兴趣爱好
    String firstHobby = rootNode.get("hobbies").get(0).asText();
    System.out.println("First hobby: " + firstHobby);
    // **修改**
    // 修改年龄为20
    ((ObjectNode) rootNode).put("age", 20);
    // 添加新的兴趣爱好:"旅行"
    ((ArrayNode) rootNode.get("hobbies")).add("旅行");
    // **合并**
    // 假设有一个新的JSON片段要与原数据合并
    String additionalJson = "{\"address\":\"北京市\",\"job\":\"程序员\"}";
    JsonNode additionalNode = mapper.readTree(additionalJson);
    // 使用ObjectNode#setAll方法将新节点中的键值对合并到原始节点中
    ((ObjectNode) rootNode).setAll((ObjectNode) additionalNode);
    // 打印更新后的JSON字符串
    System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode));
    }
    //输出
    Age: 18
    First hobby: 写代码
    {
    "name" : "小凡",
    "age" : 20,
    "hobbies" : [ "写代码", "看书", "打豆豆", "旅行" ],
    "address" : "北京市",
    "job" : "程序员"
    }

    九、Jackson注解使用

    9.1 序列化反序列化通用注解

    9.1.1 @JsonIgnore

    @JsonIgnore 用于在序列化或反序列化过程中忽略某个属性

    定义实体类Person

    public class Person {
    private String name;
    @JsonIgnore // 添加此注解以忽略 password 字段
    private String password;
    // 构造器、getter/setter 方法等...
    }

    使用示例

    @Test
    public void testJsonIgnore() throws Exception{
    Person person = new Person("小凡", "123456");
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(person);
    }
    //输出
    {
    "name": "小凡"
    }

    详细解释

    • @JsonIgnore 注解直接放在属性声明前,如 private String password; 行上方。

    • Person 类的对象被序列化为 JSON 时,password 属性将不会出现在生成的 JSON 字符串中

    • 同样地,在反序列化过程中,如果 JSON 数据中包含 password 字段,Jackson 在解析时会自动忽略它,不会尝试将其值设置到对应的 Person 对象属性上。

    9.1.2 @JsonIgnoreProperties

    @JsonIgnore 注解相比,@JsonIgnoreProperties 用于批量指定在序列化或反序列化过程中应忽略的属性列表,特别适用于应对不明确或动态变化的输入 JSON 中可能存在但不应处理的额外字段

    定义实体类Person

    希望同时忽略 passwordsocialSecurityNumber 两个敏感属性

    @JsonIgnoreProperties({"password", "socialSecurityNumber"})
    public class Person {
    private String name;
    private String password;
    private String socialSecurityNumber;
    // 构造器、getter/setter 方法等...
    }

    使用示例

    @Test
    public void testJsonIgnoreProperties() throws Exception{
    Person person = new Person("小凡", "123456","233535");
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(person);
    }
    //输出
    {
    "name": "小凡"
    }

    详细解释

    • @JsonIgnoreProperties 注解放置在类定义的开始处,作为类级别的注解。
    • 注解内通过一个字符串数组参数列举出要忽略的属性名。在这个例子中,{"password", "socialSecurityNumber"} 指定了 passwordsocialSecurityNumber 两个属性在序列化和反序列化时应被忽略。
    • 序列化 Person 对象时,生成的 JSON 字符串将不包含 passwordsocialSecurityNumber 属性。与前面 @JsonIgnore 示例类似,JSON 输出仅包含未被忽略的 name 属性:
    {
    "name": "小凡"
    }
    • 反序列化时,即使输入的 JSON 数据中包含了 passwordsocialSecurityNumber 字段,Jackson 也会忽略它们,不会尝试将这些字段的值填充到相应的 Person 对象属性中。

    @JsonIgnoreProperties 还有一个额外功能

    @JsonIgnoreProperties有个可选的属性 ignoreUnknown,用于控制是否忽略 JSON 中存在但 Java 类中没有对应的未知属性。若设置为 true,则在反序列化时遇到未知属性时会自动忽略,避免抛出异常

    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Person {
    // ...
    }
    9.1.3 @JsonIgnoreType

    @JsonIgnoreType 注解在 Jackson 库中用于指示整个类在序列化或反序列化过程中应当被忽略。这适用于那些由于某种原因(如敏感信息、内部细节等)不需要或不应该被 JSON 化处理的类

    定义CreditCardDetails 实体类

    // 标记该类在序列化和反序列化时应被忽略
    @JsonIgnoreType
    public class CreditCardDetails {
    private String cardNumber;
    private String cvv;
    private String expirationDate;
    // 构造器、getter/setter 方法等...
    }

    定义Customer

    Customer 类,它有一个 creditCardDetails 属性引用上述 CreditCardDetails 类型的对象:

    public class Customer {
    private String customerId;
    private String name;
    private CreditCardDetails creditCardDetails;
    // 构造器、getter/setter 方法等...
    }

    使用示例

    public void testJsonIgnoreType() throws JsonProcessingException {
    Customer customer = new Customer("CUST001", "John Doe", new CreditCardDetails("1234567890123456", "123", "2024-12"));
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(customer);
    }
    //输出
    {
    "customerId": "CUST001",
    "name": "John Doe"
    }

    详细解释

    • @JsonIgnoreType 注解放置在需要忽略的类定义前,作为类级别的注解。
    • Customer 类对象被序列化为 JSON 时,所有 CreditCardDetails 类型的属性(如 customer.creditCardDetails)都会被完全忽略,不会出现在生成的 JSON 字符串中。例如:

    在此例中,生成的 json 字符串将不含 creditCardDetails 部分,仅包含 customerIdname

    {
    "customerId": "CUST001",
    "name": "John Doe"
    }

    同理,在反序列化过程中,如果 JSON 数据中包含 CreditCardDetails 类型的嵌套结构,Jackson 解析时会自动忽略这部分内容,不会尝试创建或填充对应的 CreditCardDetails 对象

    9.1.4 @JsonAutoDetect

    @JsonAutoDetect 用于自定义类的属性(包括字段和 getter/setter 方法)在序列化和反序列化过程中的可见性规则。默认情况下,Jackson 只使用 public 的字段和 public 的 getter/setter 方法。通过使用 @JsonAutoDetect,你可以根据需要调整这些规则,以适应不同类的设计和数据模型。

    定义实体类 Employee

    @JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.NONE, // 不使用字段
    getterVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC, // 允许 protected 和 public 的 getter
    setterVisibility = JsonAutoDetect.Visibility.ANY, // 允许任意访问权限的 setter
    isGetterVisibility = JsonAutoDetect.Visibility.DEFAULT, // 默认行为(public)
    creatorVisibility = JsonAutoDetect.Visibility.DEFAULT) // 默认行为(public)
    public class Employee {
    protected String id;
    String department; // package-private 字段
    // 非标准命名的 getter
    public String getIdentification() {
    return id;
    }
    // 非标准命名的 setter
    public void setIdentification(String id) {
    this.id = id;
    }
    //...标准getter setter
    }

    使用示例

    @Test
    public void testJsonAutoDetect() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    Employee employee = new Employee();
    employee.setId("E001");
    employee.setDepartment("Sales");
    String json = mapper.writeValueAsString(employee);
    System.out.println(json);
    }
    //输出
    {
    "identification": "E001",
    "department": "Sales"
    }

    详细解释

    • 在上述 Employee 类的例子中:
      • fieldVisibility = JsonAutoDetect.Visibility.NONE 指定不使用任何字段,意味着即使有 public 字段,也不会被 Jackson 处理。
      • getterVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC 允许 Jackson 使用 protectedpublic 的 getter 方法。
      • setterVisibility = JsonAutoDetect.Visibility.ANY 指定可以使用任意访问权限的 setter 方法。
      • isGetterVisibilitycreatorVisibility 使用默认行为,即只处理 public 的 is-getter 方法和构造函数。
    • 因为我们将 id 字段的 getter 和 setter 设为了非标准命名(getIdentification()setIdentification()),且指定了 getterVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC,所以 Jackson 能正确识别并处理这两个方法,尽管它们不是标准的 get 前缀命名。
    9.1.5 @JsonProperty

    用于指定类的属性在序列化和反序列化成 JSON 时所对应的键名

    Book 实体类

    假设有一个 Book 类,其中包含 titleyearPublished 字段。我们希望在序列化和反序列化时,将 yearPublished 字段以 publishedYear 作为 JSON 键名。可以使用 @JsonProperty 注解进行重命名:

    public class Book {
    private String title;
    @JsonProperty("publishedYear") // 重命名 yearPublished 字段为 publishedYear
    private int yearPublished;
    // 构造器、getter/setter 方法等...
    }

    使用示例:

    现在创建一个 Book 对象并序列化为 JSON:

    public void testJsonProperty() throws Exception {
    Book book = new Book();
    book.setTitle("小凡编程语录");
    book.setYearPublished(1951);
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(book);
    System.out.println(json);
    }
    //输出
    {
    "title": "小凡编程语录",
    "publishedYear": 1951
    }

    详细解释

    在本例中,@JsonProperty("publishedYear") 表明 yearPublished 字段在 JSON 中应称为 publishedYear

    反序列化时,当遇到 JSON 中的 publishedYear 键时,Jackson 会知道应该将其值赋给 Book 类的 yearPublished 字段。

    9.1.6 @JsonFormat

    用于指定日期、时间、日期时间以及其他数值类型在序列化和反序列化为 JSON 时的格式

    创建 Student

    public class Student {
    // 其他属性...
    // 使用 @JsonFormat 注解的 LocalDate 属性
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDate birthday;
    // 构造器、getter、setter 等...
    }

    使用示例

    注:Jackson默认不支持Java 8的日期时间类型java.time.LocalDate,如果使用jdk8需要引入如下依赖.

    <dependency>
    <groupId>com.fasterxml.jackson.datatypegroupId>
    <artifactId>jackson-datatype-jsr310artifactId>
    <version>最新版本号version>
    dependency>

    除了引入以来外,还需要确保在使用Jackson进行序列化和反序列化时,注册JavaTimeModule

    mapper.registerModule(new JavaTimeModule());

    @Test
    public void testJsonFormat() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 创建 Jackson 的 ObjectMapper 实例
    mapper.registerModule(new JavaTimeModule());
    // 序列化示例
    Student student = new Student();
    student.setName("小凡");
    student.setAge(18);
    student.setBirthday(LocalDate.of(1990,1,1)); // 设置用户的出生日期
    String json = mapper.writeValueAsString(student); // 将用户对象转换为 JSON 字符串
    System.out.println(json); // 输出序列化后的 JSON 数据
    // 反序列化示例
    String inputJson = "{\"birthday\":\"1990-01-01\"}";
    Student deserializedUser = mapper.readValue(inputJson, Student.class); // 从 JSON 字符串反序列化为 User 对象
    System.out.println(deserializedUser.getBirthday()); // 输出反序列化后获得的出生日期
    }
    //输出
    {"name":"小凡","age":18,"birthday":"1990-01-01"}
    1990-01-01

    详细解释

    @JsonFormat 注解在 Jackson 中被用来精确控制日期/时间类型的属性在 JSON 序列化和反序列化过程中的格式。通过给定合适的 shapepattern 参数,可以确保日期数据在 Java 类型与 JSON 文本之间准确无误地转换。

    9.2 序列化注解

    9.2.1 @JsonInclude

    用于控制对象在序列化过程中包含哪些属性。它可以防止空或特定值的字段被序列化到JSON输出中,从而帮助您精简JSON数据结构,减少不必要的传输量或避免向客户端暴露不必要的信息

    定义PersonInclude实体类

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class PersonInclude {
    /**
    * 姓名字段,由于未指定@JsonInclude注解,因此继承类级别的NON_NULL策略
    */
    private String name;
    /**
    * 职业字段,显式指定为仅在非空时才包含在序列化结果中
    */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private String occupation;
    /**
    * 兴趣爱好列表,遵循类级别的NON_NULL策略
    */
    private List hobbies;
    // 构造函数、getter/setter等省略...
    }

    使用示例

    @Test
    public void testJsonInclude() throws JsonProcessingException {
    PersonInclude personFull = new PersonInclude();
    personFull.setName("小凡");
    personFull.setOccupation("程序员");
    personFull.setHobbies(Arrays.asList("编程","看书","打豆豆"));
    PersonInclude personPartial = new PersonInclude();
    personPartial.setName(null);
    personPartial.setOccupation("保洁员");
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(personFull);
    System.out.println(json);
    String json1 = mapper.writeValueAsString(personPartial);
    System.out.println(json1);
    }
    //输出
    {"name":"小凡","occupation":"程序员","hobbies":["编程","看书","打豆豆"]}
    {"occupation":"保洁员"}

    详细解释

    • 测试用例1:序列化后,JSON只包含nameoccupationhobbies字段都不为空,所以都显示出来
    • 测试用例2:创建了一个部分字段为null或空的PersonInclude实例。为空的不显示
    9.2.2 @JsonGetter

    用于告诉Jackson,应该通过调用getter方法而不是通过直接字段访问来获取某个字段值

    创建PersonGetter实体类

    public class PersonGetter {
    private String firstName;
    private String lastName;
    // 使用 @JsonGetter 定义一个方法,该方法将在序列化时作为 "fullName" 属性的值返回
    @JsonGetter("fullName")
    public String getFullName() {
    return this.firstName + " " + this.lastName;
    }
    // 构造函数和其他常规setter/getter方法
    }

    使用示例

    @Test
    public void testJsonGetter() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 创建 Jackson 的 ObjectMapper 实例
    PersonGetter person = new PersonGetter("张", "小凡");// 创建一个 Person 对象
    String json = mapper.writeValueAsString(person); // 将 Person 对象序列化为 JSON 字符串
    System.out.println(json); // 输出序列化后的 JSON
    }
    //输出
    {"firstName":"张","lastName":"小凡","fullName":"张 小凡"}

    @JsonGetter 注解成功地将 getFullName() 方法的结果映射到 JSON 对象中的 "fullName" 属性。

    9.2.3 @JsonAnyGetter

    用于标记一个方法,获取除已知属性外的所有其他键值对。这些键值对通常存储在一个 Map 结构中,

    以便将它们作为一个附加的对象进行序列化。

    创建CustomData实体类

    @Data
    public class CustomData {
    private final Map additionalProperties = new HashMap<>();
    // 使用 @JsonAnyGetter 定义一个方法,该方法将在序列化时返回所有额外属性
    @JsonAnyGetter
    public Map getAdditionalProperties() {
    return additionalProperties;
    }
    // 提供方法来添加或修改额外属性
    public void addProperty(String key, Object value) {
    additionalProperties.put(key, value);
    }
    }

    使用示例

    @Test
    public void testJsonAnyGetter() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 创建 Jackson 的 ObjectMapper 实例
    CustomData data = new CustomData();
    data.addProperty("key1", "value1");
    data.addProperty("key2", 42);
    data.addProperty("key3", "value3");
    String json = mapper.writeValueAsString(data); // 将 CustomData 对象序列化为 JSON 字符串
    System.out.println(json); // 输出序列化后的 JSON
    }
    //输出
    {"key1":"value1","key2":42,"key3":"value3"}

    @JsonAnyGetter 注解的主要作用如下:

    • 动态属性支持:通过在返回 Map 的方法上使用 @JsonAnyGetter,您可以将一个对象的动态属性集合序列化为 JSON 对象的多个键值对。这些属性可能是在运行时添加的,或者基于某些条件动态生成的。
    • 简化结构:避免为每个可能的动态属性单独声明字段和 getter/setter。只需维护一个 Map,即可处理任意数量和类型的额外属性。
    • 兼容性与灵活性:当需要与未知或未来可能变化的数据结构交互时,@JsonAnyGetter 可确保 JSON 表示能够容纳未预定义的属性,从而提高系统的兼容性和适应性。
    9.2.4 @JsonPropertyOrder

    用于指定类中属性在序列化为 JSON 时的排序规则

    创建OrderPerson 实体类

    public class OrderPerson {
    @JsonProperty("firstName")
    private String givenName;
    @JsonProperty("lastName")
    private String familyName;
    // 构造函数和其他常规setter/getter方法
    }

    使用示例

    @Test
    public void testJsonPropertyOrder() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 创建 Jackson 的 ObjectMapper 实例
    OrderPerson person = new OrderPerson("John", "Doe"); // 创建一个 OrderedPerson 对象
    String json = mapper.writeValueAsString(person); // 将 OrderedPerson 对象序列化为 JSON 字符串
    System.out.println(json); // 输出序列化后的 JSON
    }
    //输出
    {"firstName":"John","lastName":"Doe"}

    @JsonPropertyOrder 注解按照指定顺序("lastName""firstName")对 JSON 对象的属性进行了排序。

    9.2.5 @JsonRawValue

    用于标记一个字段或方法返回值,指示Jackson在序列化时应将其原始值视为未经转义的 JSON 字符串,并直接嵌入到输出的 JSON 文档中。即使所标记的值包含 JSON 特殊字符(如双引号、反斜杠等),也不会对其进行转义

    创建一个包含 @JsonRawValue 注解的类

    public class RawJsonValueExample {
    private String normalProperty = "这是一个正常字符串";
    // 使用 @JsonRawValue 标记 rawJsonProperty,使其内容被视为未经转义的 JSON 值
    @JsonRawValue
    private String rawJsonProperty = "{\"key\": \"value\", \"array\": [1, 2, 3]}";
    // 构造函数和其他常规setter/getter方法
    }

    使用示例

    @Test
    public void testJsonRawValue() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 创建 Jackson 的 ObjectMapper 实例
    RawJsonValueExample example = new RawJsonValueExample(); // 创建一个 RawJsonValueExample 对象
    String json = mapper.writeValueAsString(example); // 将 RawJsonValueExample 对象序列化为 JSON 字符串
    System.out.println(json); // 输出序列化后的 JSON
    }
    //输出
    {"normalProperty":"这是一个正常字符串","rawJsonProperty":{"key": "value", "array": [1, 2, 3]}}

    rawJsonProperty 的值并未被转义,而是作为一个完整的 JSON 对象直接嵌入到父 JSON 文档中。

    当您需要在 JSON 对象中嵌入另一段 JSON 文本时,使用 @JsonRawValue 可以确保这段文本以未经转义的形式出现在输出的 JSON 中,保持其原有的 JSON 结构。

    9.2.6 @JsonValue

    用于标记一个方法或字段,Jackson在序列化该类实例时,直接使用该方法的返回值或字段的值作为整个对象的 JSON 表示,而非按照类的常规属性进行序列化

    创建JsonValueExample

    public class JsonValueExample {
    private LocalDate date;
    private String formattedDate;
    @JsonValue
    public String asJsonString() {
    return "{\"date\":\"" + date.toString() + "\",\"formatted\":\"" + formattedDate + "\"}";
    }
    // 构造函数和其他常规setter/getter方法
    }

    使用示例

    @Test
    public void testJsonValue() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    JsonValueExample example = new JsonValueExample();
    example.setDate(LocalDate.of(2024,4,6));
    example.setFormattedDate("yyyy-MM-dd");
    String json = mapper.writeValueAsString(example);
    System.out.println(json);
    }
    //输出
    "{\"date\":\"2024-04-06\",\"formatted\":\"yyyy-MM-dd\"}"

    asJsonString() 方法上应用注解,指示序列化时应将该方法的返回值作为整个对象的 JSON 表达,忽略类中其他的属性

    9.2.7 @JsonSerialize

    于指定类、字段或方法在序列化过程中使用的自定义序列化逻辑

    创建一个包含 @JsonSerialize 注解的类及其自定义序列化器

    public class BooleanSerializer extends JsonSerializer {
    @Override
    public void serialize(Boolean aBoolean, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    if(aBoolean){
    jsonGenerator.writeNumber(1);
    } else {
    jsonGenerator.writeNumber(0);
    }
    }
    }
    public class PersonSerializer {
    public long personId = 0;
    public String name = "John";
    @JsonSerialize(using = BooleanSerializer.class)
    public boolean enabled = false;
    // 构造函数和其他常规setter/getter方法
    }

    使用示例

    @Test
    public void testJsonSerialize() throws JsonProcessingException {
    PersonSerializer person = new PersonSerializer();
    person.setName("小凡");
    person.setPersonId(1001);
    person.setEnabled(true);
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(person);
    System.out.println(json);
    }
    //输出
    {"personId":1001,"name":"小凡","enabled":1}

    通过上面代码测试我们可以看到,enabled被序列化为1

    9.3 反序列化注解

    9.3.1 @JsonSetter

    用于标记一个方法或字段,指示 Jackson 在反序列化过程中应如何设置该属性的值

    创建PersonSetter

    public class PersonSetter {
    @JsonSetter("first_name")
    private String firstName;
    @JsonSetter("last_name")
    private String lastName;
    // 其他getter setter toString方法省略...
    }

    使用示例

    @Test
    public void testJsonSetter() throws JsonProcessingException {
    String jsonNormal = "{\"first_name\":\"张\", \"last_name\":\"小凡\"}";
    ObjectMapper mapper = new ObjectMapper();
    PersonSetter person = mapper.readValue(jsonNormal, PersonSetter.class);
    System.out.println(person);
    }
    //输出
    PersonSetter(firstName=张, lastName=小凡)

    @JsonSetter 注解提供了灵活的方式来定制Jackson在反序列化期间如何将JSON数据映射到Java对象的属性

    9.3.2 @JsonAnySetter

    用于处理反序列化过程中遇到的未知或额外的 JSON 键值对。当一个对象的JSON表示中包含无法直接映射到已声明属性的键时,这个注解可以帮助捕获并存储这些额外的数据

    定义带有 @JsonAnySetter 注解的类

    @ToString
    public class CustomObject {
    private String knownProperty;
    private Map additionalProperties = new HashMap<>();
    // 已知属性的setter方法
    public void setKnownProperty(String knownProperty) {
    this.knownProperty = knownProperty;
    }
    // 获取已知属性的getter方法省略...
    /**
    * @JsonAnySetter注解方法,用于处理反序列化过程中遇到的所有未知属性。
    *
    * @param key 未知属性的键
    * @param value 未知属性的值
    */
    @JsonAnySetter
    public void addAdditionalProperty(String key, Object value) {
    additionalProperties.put(key, value);
    }
    // 提供访问额外属性的方法
    public Map getAdditionalProperties() {
    return additionalProperties;
    }
    }

    使用示例

    @Test
    public void testJsonAnySetter() throws JsonProcessingException {
    String json ="{\"knownProperty\":\"expectedValue\",\"extraField1\":\"someValue\",\"extraField2\":42,\"nestedObject\":{\"key\":\"value\"}}";
    ObjectMapper mapper = new ObjectMapper();
    CustomObject customObject = mapper.readValue(json, CustomObject.class);
    System.out.println(customObject);
    }
    //输出
    CustomObject(knownProperty=expectedValue, additionalProperties={extraField1=someValue, nestedObject={key=value}, extraField2=42})

    @JsonAnySetter注解来应对JSON反序列化过程中可能出现的未知属性,确保所有数据都能被妥善处理和保留。这种机制特别适用于需要兼容动态或扩展性较强的JSON输入场景。

    9.3.3 @JsonCreator

    用于标记一个构造器、静态工厂方法或实例方法,使其成为反序列化过程中创建对象实例的入口点。这个注解帮助 Jackson 确定如何根据 JSON 数据构建相应的 Java 对象。

    定义带有 @JsonCreator 注解的类

    public class AddressCreator {
    private String street;
    private int number;
    private String city;
    // 构造器上使用@JsonCreator注解,指示Jackson使用此构造器反序列化JSON
    @JsonCreator
    public AddressCreator(@JsonProperty("street") String street,
    @JsonProperty("number") int number,
    @JsonProperty("city") String city) {
    this.street = street;
    this.number = number;
    this.city = city;
    }
    // getter setter toString方法省略...
    }

    使用示例

    @Test
    public void testCreateor() throws JsonProcessingException {
    String json ="{\"street\":\"呈贡区\",\"number\":123,\"city\":\"昆明\"}";
    ObjectMapper mapper = new ObjectMapper();
    AddressCreator addressCreator = mapper.readValue(json, AddressCreator.class);
    System.out.println(addressCreator);
    }
    //输出
    AddressCreator(street=呈贡区, number=123, city=昆明)
    9.3.4 @JacksonInject

    用于在反序列化过程中自动注入依赖项或附加信息到目标对象。通常用于处理那些不能直接从 JSON 数据中获取、但又希望在反序列化完成后立即可用的信息

    定义带有 @JacksonInject 注解的类

    public class UserInject {
    private String name;
    private String email;
    // @JacksonInject注解在字段上,指示Jackson在反序列化过程中注入特定值
    @JacksonInject("defaultEmail")
    private String defaultEmail;
    // 构造器和getter setter toString方法省略...
    }

    使用示例

    @Test
    public void testJacksonInject() throws JsonProcessingException {
    // 设置可注入的值
    InjectableValues injectables = new InjectableValues.Std().addValue("defaultEmail", "xiaofan@example.com");
    // 创建ObjectMapper并配置注入值
    ObjectMapper mapper = new ObjectMapper().setInjectableValues(injectables);
    String json = "{\"name\":\"小凡\"}";
    // 反序列化时,defaultEmail字段会被自动注入指定值
    UserInject userInject = mapper.readValue(json, UserInject.class);
    System.out.println("Name: " + userInject.getName());
    System.out.println("Email: " + userInject.getEmail());
    System.out.println("Default Email: " + userInject.getDefaultEmail());
    }
    //输出
    Name: 小凡
    Email: null
    Default Email: xiaofan@example.com

    @JacksonInject 注解在Jackson反序列化过程中用于引入外部依赖或默认值,使得这些信息能够在反序列化完成后立即可用

    9.3.5 @JsonDeserialize

    用于在反序列化过程中指定自定义的反序列化器来处理某个字段或类的特殊逻辑

    自定义反序列化器

    public class BooleanDeserializer extends JsonDeserializer {
    @Override
    public Boolean deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
    String text = jsonParser.getText();
    if("0".equals(text)) {
    return false;
    }
    return true;
    }
    }

    @JsonDeserialize注解添加到要为其使用自定义反序列化器的字段

    public class PersonDeserialize {
    public long id;
    public String name;
    @JsonDeserialize(using = BooleanDeserializer.class)
    public boolean enabled = false;
    //构造器和getter setter toString方法省略...
    }

    使用示例

    {"id":1001,"name":"小凡","enabled":1}
    @Test
    public void testJsonDeserialize() throws JsonProcessingException {
    String json ="{\"id\":1001,\"name\":\"小凡\",\"enabled\":1}";
    ObjectMapper mapper = new ObjectMapper();
    PersonDeserialize personDeserialize = mapper.readValue(json, PersonDeserialize.class);
    System.out.println(personDeserialize);
    }
    //输出
    PersonDeserialize(id=1001, name=小凡, enabled=true)

    我们json字符串中enabled的值是1,最终反序列化成PersonDeserialize 对象后值变成了ture

    本期内容到此就结束了,希望对你有所帮助。我们下期再见 (●'◡'●)

  • 相关阅读:
    科学计算库—NumPy
    电脑开机屏幕闪烁,怎么解决
    Set 与 Map 的使用
    CentOS 7 上安装 MySQL 8.0详细步骤
    [论文工具] LaTeX论文SVG和EPS矢量图转换方法详解
    ChatDoctor: 在大型语言模型Meta-AI (LLaMA)上使用医学领域知识进行微调的医疗聊天模型
    昨天阅读量800多
    独家,阿里技术人限产的MySQL高级笔记及面试宝典,简直开挂
    怒怼管理层被标注“永不录用”?腾讯辟谣,应届生回应已找到新工作
    leetcode Top100 (5) 盛最多水的容器
  • 原文地址:https://www.cnblogs.com/xiezhr/p/18119893