59. SpringBoot custom JSON serializer and deserializer

Serialization: The process of saving Java objects in a disk file as a series of bytecodes. It can also be said to be the process of saving the state of Java objects. Serialization can save data permanently on disk (usually in a file).

Deserialization: Reconverting the Java bytecode stored in the disk file into a Java object is called deserialization.

★ Custom JSON serializer and deserializer

▲ 注册自定义序列化器和反序列化器有两种方式:

- 方式1:利用Jackson的模块机制来注册自定义序列化器和反序列化器。

- 方式2:利用Spring Boot提供的@JsonComponent来注册自定义序列化器和反序列化器。

第一种方式是Jackson原生的注册方式,一般不推荐。推荐使用第二种方式。

★ Use @JsonComponent to register a serializer or deserializer

Spring Boot provides the @JsonComponent annotation to register custom serializers and deserializers.
This annotation can be used in two ways:

-方式1: 直接使用 @JsonComponent 注解修饰JsonSerializer、JsonDeserializer或KeyDeserializer实现类,
  这些实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。
  @JsonComponent 注解修饰 JsonSerializer,就是自定义 JSON 序列化器
  @JsonComponent 注解修饰 JsonDeserializer,就是自定义 JSON 反序列化器
  

-方式2:使用 @JsonComponent 修饰包含JsonSerializer/JsonDeserializer内部实现类的外部类,
  这些内部实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。
  这种方式相当于将序列化器和反序列化器都定义在一个外部类中,这种方式具有更好的内聚性,
  通常推荐用方式2。

An inner class (Inner Class) is a class defined inside another class. Correspondingly, a class containing an inner class is called an outer class

Token definition
Token is the same type of field of json string defined in Fastjson, that is, "{", "[", number, string, etc., which is used to separate different fields of json string. For example, {"name": "Zhang San", "age": "20"} is a json string, which needs to be parsed into {, name, :, Zhang San, ,, age
before deserialization
, :, 20, } The Token stream of these fields is then deserialized into a response object according to the class.
Before Token parsing, the json string is just a meaningless string to the program. It is necessary to parse the json string into each Token and interpret the json data in Token units.

Code demo:

Code for custom serialization and deserializers

package cn.ljh.app.custom;


import cn.ljh.app.domain.Book;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.jackson.JsonComponent;

import java.io.IOException;

//内部类就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类,这个 CustomSeDe 就是外部类
//使用 @JsonComponent 修饰包含JsonSerializer/JsonDeserializer内部实现类的外部类,
//这些内部实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。
@JsonComponent
public class CustomSeDe
{
    
    
    //在该类中可定义自定义的序列化器和反序列化器


    //postman 用get请求时走这个方法
    //自定义序列化器,这个就是内部类
    public static class MySerializer extends JsonSerializer<Book>
    {
    
    

        @Override
        public void serialize(Book book, JsonGenerator jsonGenerator,
                              SerializerProvider serializerProvider) throws IOException
        {
    
    
            //在该方法中完成自定义的序列化输出,将对象输出成 json 或者是 xml 字符串

            jsonGenerator.writeStartObject(); //输出 {  这个花括号 , 输出对象用这个括号
//            jsonGenerator.writeStartArray();  //输出 [ 这个中括号  , 输出数值用这个括号
            jsonGenerator.writeNumberField("id", book.getId());
            //将book对象的name属性,序列化为title 属性
            jsonGenerator.writeStringField("title", book.getName());
            jsonGenerator.writeNumberField("price", book.getPrice());
            jsonGenerator.writeStringField("author", book.getAuthor());
//            jsonGenerator.writeEndArray();   // 输出这个 ] 中括号
            jsonGenerator.writeEndObject();  //输出这个 } 花括号
        }
    }

    //postman 用post的请求访问到这个方法
    //自定义反序列化器,这个就是内部类
    public static class MyDeserializer extends JsonDeserializer<Book>
    {
    
    
        //这个方法负责反序列化,就是将 json 或者是 xml 字符串 恢复成对象
        @SneakyThrows
        @Override
        public Book deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException
        {
    
    

            Book book = new Book();

            //JsonToken采用顺序方式依次读取 JSON 或 XML 字符串中的每个 token
            //token:一个花括号也叫token,一个符号隔开一个内容就叫token
            JsonToken jsonToken = jsonParser.getCurrentToken();
            //只要这个 Json 或者 xml 字符串没有结束,都应该继续向下读取
            while (!jsonToken.equals(JsonToken.END_OBJECT))
            {
    
    
                //这个循环的作用,是去保证要找到代表 Field Name 的 Token
                //如果当前读取的Token 不是字段名
                if (!jsonToken.equals(JsonToken.FIELD_NAME))
                {
    
    
                    jsonToken = jsonParser.nextToken();//读取下一个token
                    continue;
                }
                //只要程序进到此处,就一定是读取到 field name 这种字段token
                //从 JSON 或者是 XML 字符串中读取得到的字段名,也就是Book 对象的属性名
                String fieldName = jsonParser.getCurrentName();
                //读取属性名之后,下一个Token就是属性值了
                JsonToken valueToken = jsonParser.nextToken();
                //获取当前token的值
                String value = jsonParser.getText();
                //对于要逻辑处理的属性,可以在这里进行判断处理
                if (fieldName.equals("title"))
                {
    
    
                    if (!value.startsWith("《"))
                    {
    
    
                        value = "《" + value;
                    }
                    if (!value.endsWith("》"))
                    {
    
    
                        value += "》";
                    }
                    //完成了对Book对象的name属性的设置
                    book.setName(value);
                } else if (fieldName.equals("price"))
                {
    
    
                    double price = Double.parseDouble(value);
                    //把从json或xml格式的价格数据拿出来,然后打折set进去
                    book.setPrice(price * 0.5);
                } else
                {
    
    
                    //对于其他属性,不需要额外处理的话,直接通过反射来调用Book对象的fieldName对应的setter方法
                    BeanUtils.getPropertyDescriptor(Book.class, fieldName)
                            .getWriteMethod()
                            .invoke(book, value);
                }
                //继续向下读取下一个 Token
                jsonToken = jsonParser.nextToken();
            }
            return book;
        }
    }

}

test:

Serialization output is to output the object into json or xml string.
Deserialization is to restore the json or xml string into object.

When the front end sends a get request, it will use the serialization method.
http://localhost:8080/books
(I don’t know how to define whether the access method is serialization or deserialization)
The get request is to obtain the object, the serialization method is to serialize the object into json format data and return it
Insert image description here

Serialization output is to output the object into json or xml string.
Deserialization is to restore the json or xml string into object.

When making a post request, the deserialization method will be used.
The request sent is data in json format, so the deserialization method will be used to restore the data into an object.
Insert image description here

I don't quite understand how to apply this custom serializer and deserializer.

Complete code:

Book

Insert image description here

BookController

package cn.ljh.app.controller;


import cn.ljh.app.domain.Book;
import cn.ljh.app.service.BookService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/*
 *     GET  /books/{id} - (获取数据) 获取指定id的图书
 *     GET  /books?参数  -(获取数据) 获取符合查询参数的图书
 *     GET  /books        -(获取数据) 获取所有图书
 *     POST /books        -(添加数据) 添加图书
 *     PUT  /books/{id}    -(更新数据) 更新指定ID的图书
 *     DELETE /books/{id}    -(删除数据) 删除指定ID的图书
 *     DELETE /books?参数    -(删除数据) 删除符合指定参数的图书
 *
 *  Restful处理方法的返回值通常都应该使用HttpEntity或ResponseEntity。
 *
 */

@RequestMapping("/books")
@RestController
public class BookController
{
    
    
    //有参构造器进行依赖注入
    private BookService bookService;

    public BookController(BookService bookService)
    {
    
    
        this.bookService = bookService;
    }


    //根据id查看图书
    @GetMapping("/{id}")
    public ResponseEntity<Book> viewBookById(@PathVariable Integer id)
    {
    
    
        Book book = bookService.getBookById(id);

        //参数1:响应数据体  参数2:需要添加的响应头,没有就给个null   参数3:响应码 , OK 代表 200
        return new ResponseEntity<>(book, null, HttpStatus.OK);
    }

    //查看所有图书
    @GetMapping("")
    public ResponseEntity<List<Book>> viewBooks()
    {
    
    
        List<Book> allBooks = bookService.getAllBooks();

        return new ResponseEntity<>(allBooks, null, HttpStatus.OK);
    }

    //添加图书
    @PostMapping("")
    public ResponseEntity<Book> addBook(@RequestBody Book book)
    {
    
    
        Book b = bookService.addOrUpdateBook(book);
        //HttpStatus.CREATED 代表返回的状态码为 201
        return new ResponseEntity<>(b, null, HttpStatus.CREATED);
    }

    //根据id更新图书信息
    @PutMapping("/{id}")
    public ResponseEntity<Book> updateBookById(@PathVariable Integer id, @RequestBody Book book)
    {
    
    
        book.setId(id);
        Book b = bookService.addOrUpdateBook(book);

        return new ResponseEntity<>(b, null, HttpStatus.OK);
    }

    //根据id删除图书
    @DeleteMapping("/{id}")
    public ResponseEntity<Book> deleteBookById(@PathVariable Integer id)
    {
    
    
        Book book = bookService.deleteBookById(id);
        return new ResponseEntity<>(book, null, HttpStatus.OK);
    }

}

BookService

package cn.ljh.app.service;

import cn.ljh.app.domain.Book;

import java.util.List;

public interface BookService
{
    
    
    //根据id查看图书
    Book getBookById(Integer id);

    //查看所有图书
    List<Book> getAllBooks();

    //添加/修改图书
    Book addOrUpdateBook(Book book);

    //根据id删除图书
    Book deleteBookById(Integer id);

}

BookServiceImpl

package cn.ljh.app.service.impl;

import cn.ljh.app.domain.Book;
import cn.ljh.app.service.BookService;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


@Service
public class BookServiceImpl implements BookService
{
    
    
    //创建一个线程安全的map集合存数据,假设为数据库
    static Map<Integer, Book> bookDB = new ConcurrentHashMap<>();
    static int nextId = 1;

    //初始化数据库的数据
    static
    {
    
    
        bookDB.put(nextId, new Book(nextId++, "火影忍者", 120, "岸本"));
        bookDB.put(nextId, new Book(nextId++, "七龙珠", 121, "鸟山明"));
    }


    //根据id查看图书
    @Override
    public Book getBookById(Integer id)
    {
    
    
        if (id != null)
        {
    
    
            Book book = bookDB.get(id);
            if (book!=null){
    
    
                return book;
            }
        }
        throw new RuntimeException("根据id查看图书失败!");
    }

    //查看所有图书
    @Override
    public List<Book> getAllBooks()
    {
    
    
        //获取map中的所有数据
        Collection<Book> mapBooks = bookDB.values();
        //强转
        List<Book> books = new ArrayList<>(mapBooks);
        return books;
    }

    //添加/修改图书
    @Override
    public Book addOrUpdateBook(Book book)
    {
    
    
        if (book.getId() != null){
    
    
            //修改
            //map的key是唯一的,所以map里面有这个key的话,直接把原来的value覆盖掉
            bookDB.put(book.getId(),book);
            return book;
        }else {
    
    
            //新增
            //为新增的图书设置id
            book.setId(nextId);
            //book添加完之后,这个id才会自增
            bookDB.put(nextId++,book);
            return book;
        }
    }

    //根据id删除图书
    @Override
    public Book deleteBookById(Integer id)
    {
    
    
        Book book = bookDB.remove(id);
        return book;
    }
}

CustomSeDe

package cn.ljh.app.custom;


import cn.ljh.app.domain.Book;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.jackson.JsonComponent;

import java.io.IOException;

//内部类就是定义在另外一个类里面的类。与之对应,包含内部类的类被称为外部类,这个 CustomSeDe 就是外部类
//使用 @JsonComponent 修饰包含JsonSerializer/JsonDeserializer内部实现类的外部类,
//这些内部实现类将会由Spring Boot注册为自定义JSON序列化器和反序列化器。
@JsonComponent
public class CustomSeDe
{
    
    
    //在该类中可定义自定义的序列化器和反序列化器


    //postman 用get请求时走这个方法
    //自定义序列化器,这个就是内部类
    public static class MySerializer extends JsonSerializer<Book>
    {
    
    

        @Override
        public void serialize(Book book, JsonGenerator jsonGenerator,
                              SerializerProvider serializerProvider) throws IOException
        {
    
    
            //在该方法中完成自定义的序列化输出,将对象输出成 json 或者是 xml 字符串

            jsonGenerator.writeStartObject(); //输出 {  这个花括号 , 输出对象用这个括号
            //jsonGenerator.writeStartArray();  //输出 [ 这个中括号  , 输出数值用这个括号
            jsonGenerator.writeNumberField("id", book.getId());
            //将book对象的name属性,序列化为title 属性
            jsonGenerator.writeStringField("title", book.getName());
            jsonGenerator.writeNumberField("price", book.getPrice());
            jsonGenerator.writeStringField("author", book.getAuthor());
            //jsonGenerator.writeEndArray();   // 输出这个 ] 中括号
            jsonGenerator.writeEndObject();  //输出这个 } 花括号
        }
    }

    //postman 用post的请求访问到这个方法
    //自定义反序列化器,这个就是内部类
    public static class MyDeserializer extends JsonDeserializer<Book>
    {
    
    
        //这个方法负责反序列化,就是将 json 或者是 xml 字符串 恢复成对象
        @SneakyThrows
        @Override
        public Book deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException
        {
    
    

            Book book = new Book();

            //JsonToken采用顺序方式依次读取 JSON 或 XML 字符串中的每个 token
            //token:一个花括号也叫token,一个符号隔开一个内容就叫token
            JsonToken jsonToken = jsonParser.getCurrentToken();
            //只要这个 Json 或者 xml 字符串没有结束,都应该继续向下读取
            while (!jsonToken.equals(JsonToken.END_OBJECT))
            {
    
    
                //这个循环的作用,是去保证要找到代表 Field Name 的 Token
                //如果当前读取的Token 不是字段名
                if (!jsonToken.equals(JsonToken.FIELD_NAME))
                {
    
    
                    jsonToken = jsonParser.nextToken();//读取下一个token
                    continue;
                }
                //只要程序进到此处,就一定是读取到 field name 这种字段token
                //从 JSON 或者是 XML 字符串中读取得到的字段名,也就是Book 对象的属性名
                String fieldName = jsonParser.getCurrentName();
                //读取属性名之后,下一个Token就是属性值了
                JsonToken valueToken = jsonParser.nextToken();
                //获取当前token的值
                String value = jsonParser.getText();
                //对于要逻辑处理的属性,可以在这里进行判断处理
                if (fieldName.equals("title"))
                {
    
    
                    if (!value.startsWith("《"))
                    {
    
    
                        value = "《" + value;
                    }
                    if (!value.endsWith("》"))
                    {
    
    
                        value += "》";
                    }
                    //完成了对Book对象的name属性的设置
                    book.setName(value);
                } else if (fieldName.equals("price"))
                {
    
    
                    double price = Double.parseDouble(value);
                    //把从json或xml格式的价格数据拿出来,然后打折set进去
                    book.setPrice(price * 0.5);
                } else
                {
    
    
                    //对于其他属性,不需要额外处理的话,直接通过反射来调用Book对象的fieldName对应的setter方法
                    BeanUtils.getPropertyDescriptor(Book.class, fieldName)
                            .getWriteMethod()
                            .invoke(book, value);
                }
                //继续向下读取下一个 Token
                jsonToken = jsonParser.nextToken();
            }
            return book;
        }
    }

}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.5</version>
    </parent>
    <groupId>cn.ljh</groupId>
    <artifactId>CustomSeDe</artifactId>
    <version>1.0.0</version>
    <name>CustomSeDe</name>
    <properties>
        <java.version>11</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 添加 Jackson format XML , 用于对象与XML之间的转换 -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Guess you like

Origin blog.csdn.net/weixin_44411039/article/details/132777837