Jackson, the best Java JSON parser

Jackson, the best Java JSON parser

In today's programming world, JSON has become the preferred protocol for transmitting information from the client to the server. It is no exaggeration to say that XML is the front wave that was shot to death on the beach.

Unfortunately, JDK does not have a JSON library, so I don't know why I didn't do it. In Log4j, in order to compete, java.util.logging was also launched, although not many people used it in the end.

The reason why Java is so powerful is that its ecology is very complete. The JDK does not have a JSON library, and there are third-party libraries, which is pretty good. For example, the pig's feet in this article-Jackson, which is marked as 6.1k on GitHub , Spring Boot's default JSON parser.

How to prove this?

When we create a Spring Boot Web project through the starter, we can see Jackson in the Maven dependencies.

a4b41ec2e2b8e42a4ec879ef10d1edf9.jpeg

Jackson has many advantages:

  • The speed of parsing large files is relatively fast;
  • It occupies less memory and better performance when running;
  • The API is flexible and easy to extend and customize.

Jackson's core module consists of three parts:

  • jackson-core, the core package, provides related APIs based on "streaming mode" analysis, including JsonPaser and JsonGenerator.
  • jackson-annotations, annotation package, provides standard annotation function;
  • jackson-databind, data binding package, provides related APIs based on "object binding" analysis (ObjectMapper) and related APIs based on "tree model" analysis (JsonNode).

01. Introduce Jackson dependency

To use Jackson, you need to add Jackson's dependency in the pom.xml file.

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

jackson-databind depends on jackson-core and jackson-annotations, so after adding jackson-databind, Maven will automatically introduce jackson-core and jackson-annotations into the project.

74a5b31c970e2db4e7f332773e646ef4.jpeg

The reason why Maven is so likable is that it can secretly help us do what we should do.

02, use ObjectMapper

Jackson's most commonly used API is ObjectMapper based on "object binding", which serializes Java objects into JSON through a series of writeValue methods, and can store them in different formats.

  • writeValueAsString(Object value) Method to store the object as a string
  • writeValueAsBytes(Object value) Method to store the object as a byte array
  • writeValue(File resultFile, Object value) Method to store the object as a file

Take a look at a code example stored as a string:

import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;/** * 
 * * @author 
 * @date 2020/11/26 */public class Demo {
    public static void main(String[] args) throws JsonProcessingException {
        Writer wanger = new Writer("沉默王二", 18);
        ObjectMapper mapper = new ObjectMapper();
        String jsonString = mapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(wanger);
        System.out.println(jsonString);
    }}class Writer {
    private String name;
    private int age;

    public Writer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }}

The output of the program is as follows:

{ 
  "name": "Silent King Two", 
  "age": 18}

Not all fields support serialization and deserialization, and need to comply with the following rules:

  • If the modifier of the field is public, the field can be serialized and deserialized (not standard writing).
  • If the modifier of the field is not public, but its getter and setter methods are public, the field can be serialized and deserialized. The getter method is used for serialization, and the setter method is used for deserialization.
  • If the field has only a public setter method and no public getter method, the field can only be used for deserialization.

If you want to change the default serialization and deserialization rules, you need to call the ObjectMapper setVisibility()method. Otherwise, InvalidDefinitionException will be thrown.

ObjectMapper uses a series of readValue methods to deserialize JSON into Java objects from different data sources.

  • readValue(String content, Class<T> valueType) Method to deserialize a string into a Java object
  • readValue(byte[] src, Class<T> valueType) Method to deserialize the byte array into a Java object
  • readValue(File src, Class<T> valueType) Method to deserialize the file into a Java object

Let's take a look at a code example that deserializes a string into a Java object:

import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;public class Demo {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        String jsonString = "{\n" +
                "  \"name\" : \"沉默王二\",\n" +
                "  \"age\" : 18\n" +
                "}";
        Writer deserializedWriter = mapper.readValue(jsonString, Writer.class);
        System.out.println(deserializedWriter);
    }}class Writer{
    private String name;
    private int age;

    // getter/setter
    @Override
    public String toString() {
        return "Writer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }}

The output of the program is as follows:

Writer{name='Silent King Two', age=18}

PS: If the deserialized object has a constructor that takes parameters, it must have an empty default constructor, otherwise it will throw InvalidDefinitionExceptionline.

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.itwanger.jackson.Writer` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{  "name" : "沉默王二",  "age" : 18}"; line: 2, column: 3]
 at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
 at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1589)
 at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1055)
 at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1297)
 at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
 at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
 at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
 at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
 at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
 at com.itwanger.jackson.Demo.main(Demo.java:19)

Jackson’s most commonly used API is ObjectMapper based on "object binding".

ObjectMapper can also parse JSON into JsonNode objects based on the "tree model", look at the following example.

import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;public class JsonNodeDemo {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        String json = "{ \"name\" : \"沉默王二\", \"age\" : 18 }";
        JsonNode jsonNode = mapper.readTree(json);
        String name = jsonNode.get("name").asText();
        System.out.println(name); // 沉默王二    }}

With the help of TypeReference, you can convert a JSON string array into a generic List, look at the following example:

import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.core.type.TypeReference;import com.fasterxml.jackson.databind.ObjectMapper;import java.util.List;public class TypeReferenceDemo {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        String json = "[{ \"name\" : \"沉默王三\", \"age\" : 18 }, { \"name\" : \"沉默王二\", \"age\" : 19 }]";
        List<Author> listAuthor = mapper.readValue(json, new TypeReference<List<Author>>(){});
        System.out.println(listAuthor);
    }}class Author{
    private String name;
    private int age;

    // getter/setter
    // toString}

03, more advanced configuration

A very important factor in Jackson's break is that it can achieve highly flexible custom configuration.

In actual application scenarios, there are often some fields in the JSON that are not in the Java object. At this time, if it is parsed directly, an UnrecognizedPropertyException will be thrown.

Here is a string of JSON strings:

String jsonString = "{\n" +
                "  \"name\" : \"沉默王二\",\n" +
                "  \"age\" : 18\n" +
                "  \"sex\" : \"男\",\n" +
                "}";

But the sex field is not defined in the Java object Writer:

class Writer{
    private String name;
    private int age;

    // getter/setter}

Let's try to parse it:

ObjectMapper mapper = new ObjectMapper();Writer deserializedWriter = mapper.readValue(jsonString, Writer.class);

Not surprisingly, an exception was thrown and sex could not be recognized.

Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "sex" (class com.itwanger.jackson.Writer), not marked as ignorable (2 known properties: "name", "age"])
 at [Source: (String)"{  "name" : "沉默王二",  "age" : 18,  "sex" : "男"}"; line: 4, column: 12] (through reference chain: com.itwanger.jackson.Writer["sex"])

How to do it? You can configure()ignore these "Unrecognized" field method.

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

In addition, there are some other useful configuration information, to understand:

// Ignore the null property mapper during serialization.setSerializationInclusion(JsonInclude.Include.NON_NULL);// Ignore the default property mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);

04, processing date format

For date type fields, such as java.util.Date, if the format is not specified, it will be displayed as long type data after serialization. This default format is very readable.

{
  "age" : 18,
  "birthday" : 1606358621209}

How to do it?

The first option is used in the getter @JsonFormatcomment.

private Date birthday;// GMT+8 refers to Greenwich Mean Time, plus eight hours to indicate the time in your current time zone @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH: mm:ss")public Date getBirthday() { 
    return birthday;}public void setBirthday(Date birthday) { 
    this.birthday = birthday;}

Look at the results again:

{
  "age" : 18,
  "birthday" : "2020-11-26 03:02:30"}

The specific code is as follows:

ObjectMapper mapper = new ObjectMapper();Writer wanger = new Writer("沉默王二", 18);wanger.setBirthday(new Date());String jsonString = mapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(wanger);System.out.println(jsonString);

The second program, call ObjectMapper the setDateFormat()method.

ObjectMapper mapper = new ObjectMapper();mapper.setDateFormat(StdDateFormat.getDateTimeInstance());Writer wanger = new Writer("沉默王二", 18);wanger.setBirthday(new Date());String jsonString = mapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(wanger);System.out.println(jsonString);

The output is as follows:

{ 
  "name": "Silent King Two", 
  "age": 18, 
  "birthday": "11:09:51 AM on November 26, 2020"}

05, field filtering

When serializing Java objects into JSON, some fields may need to be filtered and not displayed in JSON. Jackson has a relatively simple way to implement it.

@JsonIgnore is used to filter a single field.

@JsonIgnorepublic String getName() {
    return name;}

@JsonIgnoreProperties is used to filter multiple fields.

@JsonIgnoreProperties(value = { "age","birthday" })class Writer{
    private String name;
    private int age;
    private Date birthday;}

06, custom serialization and deserialization

When Jackson's default serialization and deserialization cannot meet actual development needs, you can customize new serialization and deserialization classes.

Custom serialization need to inherit a class definition StdSerializer, to rewrite serialize()method using JsonGenerator generate the JSON, examples are as follows:

public class CustomSerializer extends StdSerializer<Man> {
    protected CustomSerializer(Class<Man> t) {
        super(t);
    }

    public CustomSerializer() {
        this(null);
    }

    @Override
    public void serialize(Man value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartObject();
        gen.writeStringField("name", value.getName());
        gen.writeEndObject();
    }}class Man{
    private int age;
    private String name;

    public Man(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }}

After defining the custom serialization classes, if you want to call them in the program, you need to register them in the Module of ObjectMapper. An example is shown below:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module =
        new SimpleModule("CustomSerializer", new Version(1, 0, 0, null, null, null));
module.addSerializer(Man.class, new CustomSerializer());
mapper.registerModule(module);
Man man = new Man( 18,"沉默王二");
String json = mapper.writeValueAsString(man);
System.out.println(json);

The output of the program is as follows:

{"name":"Silent King Two"}

The age field is not added to the custom serialization class CustomSerializer, so only the name field is output.

Then look deserialization custom classes, inheritance StdDeserializer, to rewrite data deserialize(), utilizing the JSON JsonGenerator read, examples are as follows:

public class CustomDeserializer extends StdDeserializer<Woman> {
    protected CustomDeserializer(Class<?> vc) {
        super(vc);
    }

    public CustomDeserializer() {
        this(null);
    }

    @Override
    public Woman deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode node = p.getCodec().readTree(p);
        Woman woman = new Woman();
        int age = (Integer) ((IntNode) node.get("age")).numberValue();
        String name = node.get("name").asText();
        woman.setAge(age);
        woman.setName(name);
        return woman;
    }}class Woman{
    private int age;
    private String name;

    public Woman() {
    }

    // getter/setter
    @Override
    public String toString() {
        return "Woman{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }}

Read JSON into a tree structure through JsonNode, and then read the corresponding fields through JsonNode's get method, then generate a new Java object and return it.

After defining the custom deserialization classes, if you want to call them in the program, you also need to register them in the ObjectMapper's Module. An example is shown below:

ObjectMapper mapper = new ObjectMapper();SimpleModule module =
        new SimpleModule("CustomDeserializer", new Version(1, 0, 0, null, null, null));module.addDeserializer(Woman.class, new CustomDeserializer());mapper.registerModule(module);String json = "{ \"name\" : \"三妹\", \"age\" : 18 }";Woman woman = mapper.readValue(json, Woman.class);System.out.println(woman);

The output of the program is as follows:

Woman{age=18, name='Sanmei'}

07. Conclusion

Oh, it seems pretty good, Jackson is definitely worthy of the three words "the best break". If you just want simple serialization and deserialization, use ObjectMapper's write and read methods.

If you want to go further, you need to customize the ObjectMapper configuration, or add some annotations, and directly customize the serialization and deserialization classes, which are closer to some Java objects.

It should be noted that you must be careful about the date format fields, try not to use the default configuration, the readability is very poor.

Well, through the systematic introduction of this article, I believe that readers and friends have completely understood Jackson. See you in the next article.


Author: Silence king
Original link: Jackson, most cattle breaking Java JSON parser
original source: Public Number
invasion deleted

39f85e8a3d52019b640ce9ba4c1b1f94.jpeg

Guess you like

Origin blog.51cto.com/15050718/2621819