Give up FastJson! One article is enough, Jackson's function is so awesome (4D dry goods)

In the previous article " After many researches, it was finally decided to ban FastJson! "In ", I talked about the basic use of FastJson and the existing uncertainties, so I finally decided to abandon the use in the project, and then choose the mainstream JSON library on the market that is bound by Spring Boot by default: Jackson.

This article will explain the basic use of Jackson and its combination and practice with Spring Boot.

What is Jackson

Jackson is a relatively mainstream Java-based JSON library, which can be used for serialization and deserialization between Json and XML and JavaBean.

Yes, Jackson can also handle the conversion between JavaBean and XML, based on the jackson-dataformat-xml component, and is more efficient and safer than the XML implementation that comes with JDK. And what we use more is to handle the function between JSON and JavaBean.

How mainstream is Jackson? From the statistics in the Maven warehouse alone, Jackson's usage ranks first. Among the three JSON libraries (Gson, Jackson, JSON-B) supported by Spring Boot, Jackson is the preferred default library.

Jackson also has the following characteristics: less dependency, simple and easy to use, fast parsing of large Json, low memory footprint, flexible API, and convenient expansion and customization.

Jackson class library GitHub address: https://github.com/FasterXML/jackson.

Components of Jackson

Jackson's core module consists of three parts (starting from Jackson 2.x): jackson-core, jackson-annotations, and jackson-databind.

  • jackson-core: The core package defines the low-level streaming (Streaming) API and provides analysis based on the "streaming mode". Jackson's internal implementation is to generate and parse json through the JsonGenerator and JsonParser of the high-performance streaming mode API.
  • jackson-annotations, Annotations package, provides standard Jackson annotation functions;
  • jackson-databind: Databind package, which implements data binding (and object serialization) support. It relies on Streaming and Annotations packages. Provide APIs based on "object binding" resolution (ObjectMapper) and "tree model" resolution APIs (JsonNode); API based on "object binding" resolution and API resolution of "tree model" rely on resolution based on "stream mode" API.

Let's take a look at the dependency introduction of related components in different environments.

In SpringBoot, spring-boot-starter-web indirectly introduces Jackson components, that is, if you use the SpringBoot framework, then your project already has Jackson dependencies. The following dependencies omit the version and scope items.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

The web starter relies on the json starter:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-json</artifactId>
</dependency>

json starter finally introduced Jackson:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.module</groupId>
  <artifactId>jackson-module-parameter-names</artifactId>
</dependency>

As mentioned above, jackson-databind relies on Streaming and Annotations packages, therefore, the introduction of jackson-databind is equivalent to the introduction of jackson-core and jackson-annotations.

Normally, when we use it alone, we can import jackson-databind, jackson-core and jackson-annotations through Maven as needed.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

For SpringBoot projects, there is basically no need to add additional dependencies.

Jackson core class ObjectMapper

Jackson provides three JSON processing methods: data binding, JSON tree model, and streaming API. The first two functions are implemented based on ObjectMapper, while the streaming API function needs to be implemented based on the lower-level JsonGenerator and JsonParser.

Under normal circumstances, it is sufficient to use the ObjectMapper class, which has the following functions:

  • Parse JSON from a string, stream or file, and create a Java object (deserialization) that represents the parsed JSON.
  • Construct Java objects into JSON strings (serialization).
  • Parse JSON into objects of custom classes, or parse objects of JSON tree model;

ObjectMapper is based on JsonParser and JsonGenerator to implement the actual reading/writing of JSON. This can be understood by looking at the ObjectMapper construction method.

Concrete examples

The common usage of Jackson will not be explained one by one. I will show you through a series of examples. Each example will be explained by comments.

Common and simple use

The following example is a usage demonstration that we often use, mainly involving the conversion between JavaBean and Json strings.

When Jackson converts json into JavaBean properties, the default is to bind by matching the name of the Json field with the getter and setter methods in the Java object.

Jackson removes the "get" and "set" parts from the names of getter and setter methods, and lowers the first letter. For example, the name in Json matches the getName() and setName() in JavaBean.

But not all attributes can be serialized and deserialized, basically follow the following rules:

  • Public modified attributes can be serialized and deserialized.
  • The property provides public getter/setter methods, which can be serialized and deserialized.
  • The property has only a public setter method, but no public getter method. This property can only be used for deserialization.
@Slf4j
public class JacksonTest {

    /**
     * JavaBean转JSON字符串
     */
    @Test
    public void testJavaBeanToJson() {
        WeChat weChat = new WeChat();
        weChat.setId("zhuan2quan");
        weChat.setName("程序新视界");
        weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});

        ObjectMapper mapper = new ObjectMapper();
        try {
            String result = mapper.writeValueAsString(weChat);
            System.out.println(result);
        } catch (JsonProcessingException e) {
            log.error("转换异常", e);
        }
    }

    /**
     * JSON字符串转JavaBean
     */
    @Test
    public void testJsonToJavaBean() {
        String json = "{\"id\":\"zhuan2quan\",\"name\":\"程序新视界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
        ObjectMapper mapper = new ObjectMapper();
        try {
            WeChat weChat = mapper.readValue(json, WeChat.class);
            System.out.println(weChat);
        } catch (JsonProcessingException e) {
            log.error("解析异常", e);
        }
    }

    /**
     * JSON字符串转Map集合
     */
    @Test
    public void testJsonToMap() {
        String json = "{\"id\":\"zhuan2quan\",\"name\":\"程序新视界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
        ObjectMapper mapper = new ObjectMapper();
        try {
            // 对泛型的反序列化,使用TypeReference可以明确的指定反序列化的类型。
            Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
            });
            System.out.println(map);
        } catch (JsonProcessingException e) {
            log.error("解析异常", e);
        }
    }

    /**
     * JavaBean转文件
     */
    @Test
    public void testJavaBeanToFile() {
        WeChat weChat = new WeChat();
        weChat.setId("zhuan2quan");
        weChat.setName("程序新视界");
        weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});

        ObjectMapper mapper = new ObjectMapper();
        try {
            //写到文件
            mapper.writeValue(new File("/json.txt"), weChat);

            //从文件中读取
            WeChat weChat1 = mapper.readValue(new File("/json.txt"), WeChat.class);
            System.out.println(weChat1);
        } catch (IOException e) {
            log.error("转换异常", e);
        }
    }

    /**
     * JavaBean转字节流
     */
    @Test
    public void testJavaBeanToBytes() {
        WeChat weChat = new WeChat();
        weChat.setId("zhuan2quan");
        weChat.setName("程序新视界");
        weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});

        ObjectMapper mapper = new ObjectMapper();
        try {
            // 写为字节流
            byte[] bytes = mapper.writeValueAsBytes(weChat);
            // 从字节流读取
            WeChat weChat1 = mapper.readValue(bytes, WeChat.class);
            System.out.println(weChat1);
        } catch (IOException e) {
            log.error("转换异常", e);
        }
    }
}

The above code uses lombok annotations and unit test annotations, which can be replaced as needed.

JSON tree model

If the Json string is relatively large, you can use the JSON tree model to flexibly obtain the required field content. Jackson provides get, path, has and other methods to get or judge.

Let's look directly at two examples:

@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);
        }
    }
}

The get method is similar to the path function. The difference is that if the key to be read does not exist in the Json string, the get method will be null, and the path will return the MissingNode instance object. In the case of the link method, it is guaranteed that no exception will be thrown.

Streaming API

In addition to the above two forms, you can also operate based on the underlying streaming API, mainly through the two APIs of JsonGenerator and JsonParser, but the operation is more complicated, so I will not demonstrate it here.

Format unified configuration

When using ObjectMapper, there will be some fields that do not need to be serialized or deserialized in some cases, and some formatted information may need to be specified. At this point, it can be configured through ObjectMapper.

//反序列化时忽略json中存在但Java对象不存在的属性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//序列化时日期格式默认为yyyy-MM-dd'T'HH:mm:ss.SSSZ
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//序列化时自定义时间日期格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//序列化时忽略值为null的属性
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//序列化时忽略值为默认值的属性
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);

For configuration items, a new ObjectMapper implementation class JsonMapper was added in version 2.2, which has the same function as ObjectMapper. But a builder method has been added. It can be configured directly through the JsonMapper.builder().configure() method, and finally a JsonMapper object is obtained. The other methods of JsonMapper are basically integrated from ObjectMapper.

Use of annotations

The above unified configuration can be used to configure the serialization and deserialization of the global format, but in some individual scenarios, specific fields need to be configured, which requires annotations. For example, when the fields in the Json string are inconsistent with the attributes in the Java object, an annotation is needed to establish their direct relationship.

@JsonProperty, used on the JavaBean field, specifies a field for JSON mapping. By default, the mapped JSON field has the same name as the annotated field. The field name of the mapped JSON can be specified through the value attribute.

@JsonIgnore can be used on fields, getter/setter, and constructor parameters. The specified field does not participate in serialization and deserialization.

@JsonIgnoreProperties acts on the class, @JsonIgnoreProperties({"prop1", "prop2"}) will ignore the two properties of pro1 and pro2 when serializing. @JsonIgnoreProperties(ignoreUnknown=true) will ignore fields that do not exist in the class when deserializing.

@JsonFormat acts on fields and is usually used for formatting operations.

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

If the time field in JavaBean uses the new time and date (LocalDate/LocalTime/LocalDateTime) type of JDK8, you need to add jackson-datatype-jsr310 dependency. When talking about the dependency part, there is this in the dependencies introduced by SpringBoot by default.

Of course, there are some other annotations, such as @JsonPropertyOrder, @JsonRootName, @JsonAnySetter, @JsonAnyGetter, @JsonNaming, etc. When using them, you can refer to the corresponding documents and examples to take a look. They are not listed here.

Custom parser

If the above annotations and unified configuration still cannot meet your needs, you can customize the parser. Examples are as follows:

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));
    }
}

After the definition is complete, register in the Mapper:

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

Jackson handles XML

Jackson can also provide the function of processing XML through the jackson-dataformat-xml package. It is recommended to use the woodstox-core package when processing XML. It is an XML implementation, which is more efficient and safer than the XML implementation that comes with JDK.

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

If you use Java 9 and above, java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException may occur. This is because Java 9 implements the modularization of the JDK and separates the JAXB implementation that was originally packaged with the JDK. . So you need to manually add the implementation of JAXB.

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
</dependency>

The following is a code example, which is basically very similar to the JSON API. XmlMapper is actually a subclass of ObjectMapper.

@Test
public void testXml(){
    WeChat weChat = new WeChat();
    weChat.setId("zhuan2quan");
    weChat.setName("程序新视界");
    weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});

    XmlMapper xmlMapper = new XmlMapper();
    try {
        String xml = xmlMapper.writeValueAsString(weChat);
        System.out.println(xml);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
}

After execution, the output result:

<WeChat>
    <id>zhuan2quan</id>
    <name>程序新视界</name>
    <interest>
        <interest>Java</interest>
        <interest>Spring Boot</interest>
        <interest>JVM</interest>
    </interest>
</WeChat>

Integration in Spring Boot

At the very beginning, we have seen that Spring Boot introduces Jackson's dependency by default, and we also do additional operations with us. In fact, we are already using Jackson to bind data in Json format with parameters in MVC.

If the default configuration of Spring Boot is not suitable for the project requirements, you can also configure it through the built-in configuration. Taking the application.yml configuration as an example, you can configure the corresponding options by specifying the following properties:

#指定日期格式,比如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 automatic configuration is very convenient, but sometimes we need to manually configure the Bean to replace the automatically configured Bean. It can be configured in the following form:

@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;
    }
}

After the configuration is complete, you can directly inject it where it is used:

@Resource
private ObjectMapper jsonMapper;

For the above injection, some friends may ask, is there a thread safety issue? Don't worry about ObjectMapper being thread-safe.

summary

After the explanation of this article, everyone should have a more comprehensive understanding of Jackson. Personally, after learning Jackson, it still feels quite interesting.

Original link: " Abandon FastJson! One article is enough, Jackson's function is so awesome (4D dry goods)


New Vision of Procedure

The public account " New Vision of Program ", a platform that allows you to simultaneously improve your soft power and hard technology, providing massive amounts of data

WeChat Official Account: A New Vision of Program

Guess you like

Origin blog.csdn.net/wo541075754/article/details/114006957