1. 程式人生 > 其它 >Spring Boot中的JSON技術【轉】

Spring Boot中的JSON技術【轉】

平日裡在專案中處理JSON一般用的都是阿里巴巴的Fastjson,後來發現使用Spring Boot內建的Jackson來完成JSON的序列化和反序列化操作也挺方便。Jackson不但可以完成簡單的序列化和反序列化操作,也能實現複雜的個性化的序列化和反序列化操作。

自定義ObjectMapper

我們都知道,在Spring中使用@ResponseBody註解可以將方法返回的物件序列化成JSON,比如:

@RequestMapping("getuser")
@ResponseBody
public User getUser() {
    User user = new User();
    user.setUserName("mrbird");
    user.setBirthday(new Date());
    return user;
}

User類:

public class User implements Serializable {
    private static final long serialVersionUID = 6222176558369919436L;
    
    private String userName;
    private int age;
    private String password;
    private Date birthday;
    ...
}

訪問getuser頁面輸出:

{"userName":"mrbird","age":0,"password":null,"birthday":1522634892365}

可看到時間預設以時間戳的形式輸出,如果想要改變這個預設行為,我們可以自定義一個ObjectMapper來替代:

import java.text.SimpleDateFormat;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper getObjectMapper(){
        ObjectMapper mapper = new ObjectMapper();
        mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        return mapper;
    }
}

上面配置獲取了ObjectMapper物件,並且設定了時間格式。再次訪問getuser,頁面輸出:

{"userName":"mrbird","age":0,"password":null,"birthday":"2018-04-02 10:14:24"}

序列化

Jackson通過使用mapper的writeValueAsString方法將Java物件序列化為JSON格式字串:

@Autowired
ObjectMapper mapper;

@RequestMapping("serialization")
@ResponseBody
public String serialization() {
    try {
        User user = new User();
        user.setUserName("mrbird");
        user.setBirthday(new Date());
        String str = mapper.writeValueAsString(user);
        return str;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

反序列化

使用@ResponseBody註解可以使物件序列化為JSON格式字串,除此之外,Jackson也提供了反序列化方法。

樹遍歷

當採用樹遍歷的方式時,JSON被讀入到JsonNode物件中,可以像操作XML DOM那樣讀取JSON。比如:

@Autowired
ObjectMapper mapper;

@RequestMapping("readjsonstring")
@ResponseBody
public String readJsonString() {
    try {
        String json = "{\"name\":\"mrbird\",\"age\":26}";
        JsonNode node = this.mapper.readTree(json);
        String name = node.get("name").asText();
        int age = node.get("age").asInt();
        return name + " " + age;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

readTree方法可以接受一個字串或者位元組陣列、檔案、InputStream等, 返回JsonNode作為根節點,你可以像操作XML DOM那樣操作遍歷JsonNode以獲取資料。

解析多級JSON例子:

String json = "{\"name\":\"mrbird\",\"hobby\":{\"first\":\"sleep\",\"second\":\"eat\"}}";;
JsonNode node = this.mapper.readTree(json);
JsonNode hobby = node.get("hobby");
String first = hobby.get("first").asText();

繫結物件

我們也可以將Java物件和JSON資料進行繫結,如下所示:

@Autowired
ObjectMapper mapper;

@RequestMapping("readjsonasobject")
@ResponseBody
public String readJsonAsObject() {
    try {
        String json = "{\"name\":\"mrbird\",\"age\":26}";
        User user = mapper.readValue(json, User.class);
        String name = user.getUserName();
        int age = user.getAge();
        return name + " " + age;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

Jackson註解

Jackson包含了一些實用的註解:

@JsonProperty

@JsonProperty,作用在屬性上,用來為JSON Key指定一個別名。

@JsonProperty("bth")
private Date birthday;

再次訪問getuser頁面輸出:

{"userName":"mrbird","age":0,"password":null,"bth":"2018-04-02 10:38:37"}

key birthday已經被替換為了bth。

@Jsonlgnore

@Jsonlgnore,作用在屬性上,用來忽略此屬性。

@JsonIgnore
private String password;

再次訪問getuser頁面輸出:

{"userName":"mrbird","age":0,"bth":"2018-04-02 10:40:45"}

password屬性已被忽略。

@JsonIgnoreProperties

@JsonIgnoreProperties,忽略一組屬性,作用於類上,比如JsonIgnoreProperties({ "password", "age" })。

@JsonIgnoreProperties({ "password", "age" })
public class User implements Serializable {
    ...
}

再次訪問getuser頁面輸出:

{"userName":"mrbird","bth":"2018-04-02 10:45:34"}

@JsonFormat

@JsonFormat,用於日期格式化,如:

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date birthday;

@JsonNaming

@JsonNaming,用於指定一個命名策略,作用於類或者屬性上。Jackson自帶了多種命名策略,你可以實現自己的命名策略,比如輸出的key 由Java命名方式轉為下面線命名方法 —— userName轉化為user-name。

@JsonNaming(PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy.class)
public class User implements Serializable {
    ...
}

再次訪問getuser頁面輸出:

{"user_name":"mrbird","bth":"2018-04-02 10:52:12"}

@JsonSerialize

@JsonSerialize,指定一個實現類來自定義序列化。類必須實現JsonSerializer介面,程式碼如下:

import java.io.IOException;

import com.example.pojo.User;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class UserSerializer extends JsonSerializer<User> {

    @Override
    public void serialize(User user, JsonGenerator generator, SerializerProvider provider)
            throws IOException, JsonProcessingException {
        generator.writeStartObject();
        generator.writeStringField("user-name", user.getUserName());
        generator.writeEndObject();
    }
}

上面的程式碼中我們僅僅序列化userName屬性,且輸出的key是user-name。 使用註解@JsonSerialize來指定User物件的序列化方式:

@JsonSerialize(using = UserSerializer.class)
public class User implements Serializable {
    ...
}

再次訪問getuser頁面輸出:

{"user-name":"mrbird"}

@JsonDeserialize

@JsonDeserialize,使用者自定義反序列化,同@JsonSerialize ,類需要實現JsonDeserializer介面。

import java.io.IOException;

import com.example.pojo.User;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;

public class UserDeserializer extends JsonDeserializer<User> {

    @Override
    public User deserialize(JsonParser parser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        JsonNode node = parser.getCodec().readTree(parser);
        String userName = node.get("user-name").asText();
        User user = new User();
        user.setUserName(userName);
        return user;
    }
}

使用註解@JsonDeserialize來指定User物件的序列化方式:

@JsonDeserialize (using = UserDeserializer.class)
public class User implements Serializable {
    ...
}

測試:

@Autowired
ObjectMapper mapper;

@RequestMapping("readjsonasobject")
@ResponseBody
public String readJsonAsObject() {
    try {
        String json = "{\"user-name\":\"mrbird\"}";
        User user = mapper.readValue(json, User.class);
        String name = user.getUserName();
        return name;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

訪問readjsonasobject,頁面輸出:

mrbird

@JsonView

@JsonView,作用在類或者屬性上,用來定義一個序列化組。 比如對於User物件,某些情況下只返回userName屬性就行,而某些情況下需要返回全部屬性。 因此User物件可以定義成如下:

public class User implements Serializable {
    private static final long serialVersionUID = 6222176558369919436L;
    
    public interface UserNameView {};
    public interface AllUserFieldView extends UserNameView {};
    
    @JsonView(UserNameView.class)
    private String userName;
    
    @JsonView(AllUserFieldView.class)
    private int age;
    
    @JsonView(AllUserFieldView.class)
    private String password;
    
    @JsonView(AllUserFieldView.class)
    private Date birthday;
    ...	
}

User定義了兩個介面類,一個為userNameView,另外一個為AllUserFieldView繼承了userNameView介面。這兩個介面代表了兩個序列化組的名稱。屬性userName使用了@JsonView(UserNameView.class),而剩下屬性使用了@JsonView(AllUserFieldView.class)。

Spring中Controller方法允許使用@JsonView指定一個組名,被序列化的物件只有在這個組的屬性才會被序列化,程式碼如下:

@JsonView(User.UserNameView.class)
@RequestMapping("getuser")
@ResponseBody
public User getUser() {
    User user = new User();
    user.setUserName("mrbird");
    user.setAge(26);
    user.setPassword("123456");
    user.setBirthday(new Date());
    return user;
}

訪問getuser頁面輸出:

{"userName":"mrbird"}

如果將@JsonView(User.UserNameView.class)替換為@JsonView(User.AllUserFieldView.class),輸出:

{"userName":"mrbird","age":26,"password":"123456","birthday":"2018-04-02 11:24:00"}

因為介面AllUserFieldView繼承了介面UserNameView所以userName也會被輸出。

集合的反序列化

在Controller方法中,可以使用@RequestBody將提交的JSON自動對映到方法引數上,比如:

@RequestMapping("updateuser")
@ResponseBody
public int updateUser(@RequestBody List<User> list){
    return list.size();
}

上面方法可以接受如下一個JSON請求,並自動對映到User物件上:

[{"userName":"mrbird","age":26},{"userName":"scott","age":27}]

Spring Boot 能自動識別出List物件包含的是User類,因為在方法中定義的泛型的型別會被保留在位元組碼中,所以Spring Boot能識別List包含的泛型型別從而能正確反序列化。

有些情況下,集合物件並沒有包含泛型定義,如下程式碼所示,反序列化並不能得到期望的結果。

@Autowired
ObjectMapper mapper;

@RequestMapping("customize")
@ResponseBody
public String customize() throws JsonParseException, JsonMappingException, IOException {
    String jsonStr = "[{\"userName\":\"mrbird\",\"age\":26},{\"userName\":\"scott\",\"age\":27}]";
    List<User> list = mapper.readValue(jsonStr, List.class);
    String msg = "";
    for (User user : list) {
        msg += user.getUserName();
    }
    return msg;
}

訪問customize,控制檯丟擲異常:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.pojo.User

這是因為在執行時刻,泛型己經被擦除了(不同於方法引數定義的泛型,不會被擦除)。為了提供泛型資訊,Jackson提供了JavaType ,用來指明集合型別,將上述方法改為:

@Autowired
ObjectMapper mapper;

@RequestMapping("customize")
@ResponseBody
public String customize() throws JsonParseException, JsonMappingException, IOException {
    String jsonStr = "[{\"userName\":\"mrbird\",\"age\":26},{\"userName\":\"scott\",\"age\":27}]";
    JavaType type = mapper.getTypeFactory().constructParametricType(List.class, User.class);
    List<User> list = mapper.readValue(jsonStr, type);
    String msg = "";
    for (User user : list) {
        msg += user.getUserName();
    }
    return msg;
}

訪問customize,頁面輸出:mrbirdscott。

原始碼連線https://github.com/wuyouzhuguli/Spring-Boot-Demos/tree/master/18.Spring-Boot-Jackson

本文作者: MrBird
本文連結: http://mrbird.cc/Spring-Boot JSON.html
版權宣告: 本部落格所有文章除特別宣告外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明出處!