Converting entities to json data format -- detailed usage of Jackson_ Jackson quick start

First, three notes are introduced:

@JsonAutoDetect (method/field): acts on a method or field to indicate that the method or field with the annotation is ignored when generating json

@JsonIgnore filters attributes that do not need to be converted to json

@JsonIgnoreProperties is mainly used to filter out some unnecessary properties

The above three annotations need to be placed in front of the get method to be effective

package com.sw.entity.base;
 
import static javax.persistence.GenerationType.IDENTITY;
 
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
 
import org.codehaus.jackson.annotate.JsonAutoDetect;
import org.codehaus.jackson.annotate.JsonIgnore;
 
/**
 * Menu entity. @author MyEclipse Persistence Tools
 */
@Entity
@Table(name = "menu", catalog = "zhong")
@JsonAutoDetect
public class Menu implements java.io.Serializable {
 
    // Fields
 
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private Integer id;
    private Menu menu;
    private String icon;
    private String name;
    private Short seq;
    private String url;
    private String prem;
    private Set<Menu> menus = new HashSet<Menu>(0);
 
    // Constructors
     
    /**Attributes for json output do not need to be persisted;*/
    private String state = "open";
     
    /*********************************/
     
    /** default constructor */
    public Menu() {
    }
 
    /** minimal constructor */
    public Menu(Integer id) {
        this.id = id;
    }
 
    /** full constructor */
//Omit constructor
 
    // Property accessors
 
/**The get/set method that does not need to be processed is omitted*/
 
    @JsonIgnore  //Marking this attribute does not need to be converted to json
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "pid")
    public Menu getMenu() {
        return this.menu;
    }
 
    public void setMenu(Menu menu) {
        this.menu = menu;
    }
 
     
 
    @JsonIgnore //Marking this attribute does not need to be converted to json
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "menu")
    public Set<Menu> getMenus() {
        return this.menus;
    }
 
    public void setMenus(Set<Menu> menus) {
        this.menus = menus;
    }
 
     
 
 
 /*jackson The property value is obtained according to the get method. If the persistent property value does not meet your requirements,
You can do this by changing the get method.
For example, your entity has a set < XXX > XXX; According to jackson's original plan, the collection type will be converted to json array, but if you want to convert the content of this collection into a string, you can return characters through the get method. Or return anything at your request.*/
 
  //This is to mark the field in the tree structure menu. Adding this annotation means that the field does not need to persist the entity to the database
          @Transient
    public String getState() {
        if(this.getMenus()==null){
            state = "open";
        }else{
            state = "closed";
        }
        return state;
    }
 
    public void setState(String state) {
        this.state = state;
    }
     
     
 
}

The following is the conversion code:

List<Menu> menus = menuManager.getChildrenById(aid,"pid","seq",true); //Fetch list from database
ObjectMapper map = new ObjectMapper();//This is jackson's class library
map.writeValue(Struts2Utils.getResponse().getWriter(),menus); //Output json so that json

The data is output to the browser client.

Here are the values:

[{"name":"dfdsf","id":1,"state":"closed","seq":999,"prem":"fsfsd","icon":"fff","url":"gfdsfds"},
{"name":"fdsfds","id":2,"state":"closed","seq":999,"prem":"fsdf","icon":"fff","url":"efdsfsd"}]
jackson Common transformation forms:
writeValueAsString(value) Translate into String
writeValueAsBytes(value) Translate into byte
writeValue(objec0,bean) This method is used for direct output json data
objec0 Represents the output environment,Can be Response.getWriter()It can also be Outputstream.bean Is the data to be output,Set and single bean Fine

Jackson quick start

There are many class libraries dealing with JSON and XML formatting in the Java ecosystem, and Jackson is one of them. Although JDK has its own XML processing class library, it is relatively low-level. It will be much more convenient to use Jackson and other high-level class libraries introduced in this article.

Import class library

Because Jackson related class libraries are divided into several relatively independent ones according to functions, multiple class libraries need to be introduced at the same time. In order to facilitate me to extract and set the version number separately, the related Gradle configuration is as follows.

ext {
    jacksonVersion = '2.9.5'
}

dependencies {
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: jacksonVersion
    // Introducing XML functionality
    compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: jacksonVersion
    // More efficient class library than the XML implementation of JDK
    compile group: 'com.fasterxml.woodstox', name: 'woodstox-core', version: '5.1.0'
    // New features of Java 8
    compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: jacksonVersion
    compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-parameter-names', version: jacksonVersion
    compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jdk8', version: jacksonVersion

    compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.16.22'
}

Maven configuration, please go mvnrepository Search.

Jackson notes

The Jackson class library contains many annotations that allow us to quickly establish the relationship between Java classes and JSON. Detailed documents can be referred to Jackson-Annotations . Here are some common.

Attribute naming

@The JsonProperty annotation specifies a property for JSON mapping. By default, the mapped JSON property is the same as the property name of the annotation, but the JSON property name can be modified by using the value value of the annotation. The annotation also has an index property to specify the order of generating JSON properties, if necessary.

Attribute contains

There are also some annotations that can manage the inclusion or exclusion of some attributes when mapping JSON. The following describes some common annotations.

@The JsonIgnore annotation is used to exclude an attribute so that it will not be serialized and deserialized by Jackson.

@The JsonIgnoreProperties annotation is a class annotation. When serializing to JSON, @ JsonIgnoreProperties({"prop1", "prop2"}) will ignore pro1 and pro2 properties. When deserializing from JSON to Java class, @ JsonIgnoreProperties(ignoreUnknown=true) will ignore all properties without Getter and Setter. This annotation is useful when Java classes and JSON do not exactly match.

@JsonIgnoreType is also a class annotation that excludes all attributes of the specified type.

Serialization correlation

@JsonPropertyOrder is similar to the index property of @ JsonProperty, which specifies the order of property serialization.

@The JsonRootName annotation is used to specify the name of the JSON root attribute.

Handle JSON

Simple mapping

We set up a simple Java class with Lombok.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Friend {
    private String nickname;
    private int age;
}

Then you can process JSON data. First, you need an ObjectMapper object, which is required for both serialization and deserialization.

        ObjectMapper mapper = new ObjectMapper();
        Friend friend = new Friend("yitian", 25);

        // Write as string
        String text = mapper.writeValueAsString(friend);
        // Write as file
        mapper.writeValue(new File("friend.json"), friend);
        // Write as byte stream
        byte[] bytes = mapper.writeValueAsBytes(friend);
        System.out.println(text);
        // Read from string
        Friend newFriend = mapper.readValue(text, Friend.class);
        // Read from byte stream
        newFriend = mapper.readValue(bytes, Friend.class);
        // Read from file
        newFriend = mapper.readValue(new File("friend.json"), Friend.class);
        System.out.println(newFriend);

The program results are as follows. You can see that the generated JSON attributes are consistent with those defined in the Java class.

{"nickname":"yitian","age":25}
Friend(nickname=yitian, age=25)

Set mapping

In addition to using java classes for mapping, we can also directly use Java collections such as Map and List to organize JSON data. When necessary, we can use the readTree method to directly read a property value in JSON. It should be noted that when converting from JSON to Map object, because the type of Java is erased, the type needs to be given manually with new TypeReference.

        ObjectMapper mapper = new ObjectMapper();

        Map<String, Object> map = new HashMap<>();
        map.put("age", 25);
        map.put("name", "yitian");
        map.put("interests", new String[]{"pc games", "music"});

        String text = mapper.writeValueAsString(map);
        System.out.println(text);

        Map<String, Object> map2 = mapper.readValue(text, new TypeReference<Map<String, Object>>() {
        });
        System.out.println(map2);

        JsonNode root = mapper.readTree(text);
        String name = root.get("name").asText();
        int age = root.get("age").asInt();

        System.out.println("name:" + name + " age:" + age);

The program results are as follows.

{"name":"yitian","interests":["pc games","music"],"age":25}
{name=yitian, interests=[pc games, music], age=25}
name:yitian age:25

Jackson configuration

Jackson predefines some configurations. We can modify some behaviors of Jackson by enabling and disabling some properties. Detailed document reference JacksonFeatures . Let me briefly translate some of the attributes listed on Jackson README.

// Beautify output
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// Allow serialization of empty POJO classes
// (otherwise, an exception will be thrown)
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// Put Java util. Date, calendar output as number (timestamp)
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

// No exception is thrown when an unknown attribute is encountered
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// Force JSON empty string ('') to convert to null object value:
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

// C/C + + style annotations are allowed in JSON (non-standard, disabled by default)
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
// Allow field names without quotation marks (non-standard)
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// Allow single quotation marks (non-standard)
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// Force escape of non ASCII characters
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
// Wrap the content into a JSON attribute whose name is specified by the @ JsonRootName annotation
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);

There are three methods. The configure method accepts the configuration name and the value to be set. The enable and disable methods added in Jackson version 2.5 directly enable and disable the corresponding properties. I recommend the latter two methods.

Manage mappings with annotations

Some Jackson annotations were introduced earlier. Let's apply these annotations. First, let's take a look at the Java classes that use annotations.

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonRootName("FriendDetail")
@JsonIgnoreProperties({"uselessProp1", "uselessProp3"})
public class FriendDetail {
    @JsonProperty("NickName")
    private String name;
    @JsonProperty("Age")
    private int age;
    private String uselessProp1;
    @JsonIgnore
    private int uselessProp2;
    private String uselessProp3;
}

Then look at the code. It should be noted that the generated JSON and Java classes do not correspond exactly because the excluded attribute is set, so the deserialization feature is disabled FAIL_ ON_ UNKNOWN_ Properties is necessary.

        ObjectMapper mapper = new ObjectMapper();
        //mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        FriendDetail fd = new FriendDetail("yitian", 25, "", 0, "");
        String text = mapper.writeValueAsString(fd);
        System.out.println(text);

        FriendDetail fd2 = mapper.readValue(text, FriendDetail.class);
        System.out.println(fd2);

The operation results are as follows. We can see that the value we set is ignored when generating JSON, and the corresponding attribute is empty when converting to Java class.

{"NickName":"yitian","Age":25}
FriendDetail(name=yitian, age=25, uselessProp1=null, uselessProp2=0, uselessProp3=null)

Then uncomment the line in the code, that is, enable WRAP_ROOT_VALUE function, run the program again, and the running results are as follows. You can see that the generated JSON result has changed, and because the JSON result has changed, the Java class conversion fails (all field values are empty). WRAP_ROOT_VALUE is useful sometimes because some JSON files need this structure.

{"FriendDetail":{"NickName":"yitian","Age":25}}
FriendDetail(name=null, age=0, uselessProp1=null, uselessProp2=0, uselessProp3=null)

Java 8 date time class support

Java 8 adds a new set of date and time classes, which Jackson also supports. These supports are provided in the form of Jackson modules, so the first step is to register these modules.

        ObjectMapper mapper = new ObjectMapper()
                .registerModule(new JavaTimeModule())
                .registerModule(new ParameterNamesModule())
                .registerModule(new Jdk8Module());

After importing the class library, Jackson can also automatically search all modules without requiring us to register manually.

        mapper.findAndRegisterModules();

Let's create a new Java class with a LocalDate field.

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonRootName("Person")
public class Person {
    @JsonProperty("Name")
    private String name;
    @JsonProperty("NickName")
    private String nickname;
    @JsonProperty("Age")
    private int age;
    @JsonProperty("IdentityCode")
    private String identityCode;
    @JsonProperty
    @JsonFormat(pattern = "yyyy-MM-DD")
    private LocalDate birthday;
}

Then take a look at the code.

    static void java8DateTime() throws IOException {
        Person p1 = new Person("yitian", "Yi Tian", 25, "10000", LocalDate.of(1994, 1, 1));
        ObjectMapper mapper = new ObjectMapper()
                .registerModule(new JavaTimeModule());
        //mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        String text = mapper.writeValueAsString(p1);
        System.out.println(text);

        Person p2 = mapper.readValue(text, Person.class);
        System.out.println(p2);
    }

The operation results are as follows. It can be seen that the generated JSON date has become a timestamp like [1994,1,1], which generally does not meet our requirements.

{"birthday":[1994,1,1],"Name":"yitian","NickName":"Yi Tian","Age":25,"IdentityCode":"10000"}
Person(name=yitian, nickname=Yi Tian, age=25, identityCode=10000, birthday=1994-01-01)

Uncomment that line of code, and the program runs as follows. In this way, it has become the form we usually use. If format is required, you can use @ jsonformat (pattern = "yyyy MM DD") annotation to format the date display.

{"birthday":"1994-01-01","Name":"yitian","NickName":"Yi Tian","Age":25,"IdentityCode":"10000"}
Person(name=yitian, nickname=Yi Tian, age=25, identityCode=10000, birthday=1994-01-01)

Processing XML

Jackson is a class library that handles JSON, but it also provides the ability to handle XML through the Jackson dataformat XML package. Jackson suggested that we use woodstox core package when dealing with XML. It is an XML implementation, which is more efficient and secure than the XML implementation of JDK.

Here's a note. If you are using JDK above Java 9, Java. Net may appear Lang.noclassdeffounderror: javax / XML / bind / jaxbexception exception. This is because Java 9 implements the modularization of JDK and separates the JAXB implementation originally packaged with JDK. So at this time, we need to manually add the implementation of JAXB. Add the following code to Gradle.

compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0'

annotation

In addition to using some annotations of Jackson JSON and JDK JAXB, Jackson XML also defines some annotations. Here are some common annotations.

@The JacksonXmlProperty annotation has three attributes. The namespace and localname attributes are used to specify the name of the XML namespace. isAttribute specifies whether the attribute is an XML attribute (< a B = "XXX" > < / a >) or a sub tag (< a > < b > < / b > < / A >)

@The Jackson xmlrootelement annotation has two attributes, namespace and localname, which specify the name of the XML root element namespace.

@The JacksonXmlText annotation represents the attribute directly as plain text that is not wrapped by the tag.

@Jackson xmlcdata wraps attributes in CDATA tags.

XML Mapping

Create the following Java class.

@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonRootName("Person")
public class Person {
    @JsonProperty("Name")
    private String name;
    @JsonProperty("NickName")
    //@JacksonXmlText
    private String nickname;
    @JsonProperty("Age")
    private int age;
    @JsonProperty("IdentityCode")
    @JacksonXmlCData
    private String identityCode;
    @JsonProperty("Birthday")
    //@JacksonXmlProperty(isAttribute = true)
    @JsonFormat(pattern = "yyyy/MM/DD")
    private LocalDate birthday;

}

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

        Person p1 = new Person("yitian", "Yi Tian", 25, "10000", LocalDate.of(1994, 1, 1));
        XmlMapper mapper = new XmlMapper();
        mapper.findAndRegisterModules();
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        String text = mapper.writeValueAsString(p1);
        System.out.println(text);

        Person p2 = mapper.readValue(text, Person.class);
        System.out.println(p2);

The operation results are as follows.

<Person>
  <Name>yitian</Name>
  <NickName>Yi Tian</NickName>
  <Age>25</Age>
  <IdentityCode><![CDATA[10000]]></IdentityCode>
  <Birthday>1994/01/01</Birthday>
</Person>

Person(name=yitian, nickname=Yi Tian, age=25, identityCode=10000, birthday=1994-01-01)

If you uncomment those two lines, the result is as follows. You can see the control effect of Jackson XML annotation on the generated XML.

<Person birthday="1994/01/01">
  <Name>yitian</Name>Yi Tian
  <Age>25</Age>
  <IdentityCode><![CDATA[10000]]></IdentityCode>
</Person>

Person(name=yitian, nickname=null, age=25, identityCode=10000, birthday=1994-01-01)

Spring Boot integration

Auto configuration

Spring Boot has perfect support for Jackson. As long as we introduce the corresponding class library, Spring Boot can automatically configure beans out of the box. The ObjectMapper (or XmlMapper) automatically configured by spring is configured as follows, which can basically adapt to most situations.

  • Mapperfeature. Is disabled DEFAULT_ VIEW_ INCLUSION
  • Deserializationfeature is disabled FAIL_ ON_ UNKNOWN_ PROPERTIES
  • Serializationfeature. Is disabled WRITE_ DATES_ AS_ TIMESTAMPS

If you need to modify the ObjectMapper property of automatic configuration, it is also very simple. Spring Boot provides a set of environment variables directly in application You can modify it in the properties file.

Jackson enumerationSpring environment variables
com.fasterxml.jackson.databind.DeserializationFeaturespring.jackson.deserialization.<feature_name>=true|false
com.fasterxml.jackson.core.JsonGenerator.Featurespring.jackson.generator.<feature_name>=true|false
com.fasterxml.jackson.databind.MapperFeaturespring.jackson.mapper.<feature_name>=true|false
com.fasterxml.jackson.core.JsonParser.Featurespring.jackson.parser.<feature_name>=true|false
com.fasterxml.jackson.databind.SerializationFeaturespring.jackson.serialization.<feature_name>=true|false
com.fasterxml.jackson.annotation.JsonInclude.Includespring.jackson.default-property-inclusion=always|non_null|non_absent|non_default|non_empty

Since Spring will configure the corresponding HttpMessageConverters at the same time, what we need to do is very simple. Mark the Java class to be mapped with Jackson annotation, and then directly let the controller return the object! Here is a Java class.

@JsonRootName("person")
public class Person {
    @JsonProperty
    private String name;
    @JsonProperty
    private int id;
    @JsonFormat(pattern = "yyyy-MM-DD")
    private LocalDate birthday;

    public Person(String name, int id, LocalDate birthday) {
        this.name = name;
        this.id = id;
        this.birthday = birthday;
    }
}

Then there is the controller code. In the whole process, we only need to introduce Jackson class library and write business code. We don't need to manage how to configure Jackson class library at all, which is the convenience of Spring Boot.

@Controller
public class MainController {
    private Person person = new Person("yitian", 10000, LocalDate.of(1994, 1, 1));

    @RequestMapping("/")
    public String index() {
        return "index";
    }


    @RequestMapping(value = "/json", produces = "application/json")
    @ResponseBody
    public Person json() {
        return person;
    }
}

You can see the corresponding result of 80xml / local: host.

result

Manual configuration

Spring Boot automatic configuration is very convenient, but it is not omnipotent. When necessary, we need to manually configure beans to replace automatically configured beans.

@Configuration
public class JacksonConfig {
    @Bean
    @Primary
    @Qualifier("xml")
    public XmlMapper xmlMapper(Jackson2ObjectMapperBuilder builder) {
        XmlMapper mapper = builder.createXmlMapper(true)
                .build();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }

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

Then, dependency injection is carried out where necessary. Note that to distinguish between ObjectMapper and XmlMapper, you need to use the @ Qualifier annotation.

@Controller
public class MainController {
    private ObjectMapper jsonMapper;
    private XmlMapper xmlMapper;
    private Person person = new Person("yitian", 10000, LocalDate.of(1994, 1, 1));

    public MainController(@Autowired @Qualifier("json") ObjectMapper jsonMapper, @Autowired @Qualifier("xml") XmlMapper xmlMapper) {
        this.jsonMapper = jsonMapper;
        this.xmlMapper = xmlMapper;
    }

The above is some introduction of Jackson class library. I hope it will be helpful to you.

Tags: Java JSON jackson

Posted by szz on Sun, 15 May 2022 07:05:46 +0300