FastJson简介

首先,介绍一下fastjson。fastjson是由alibaba开源的一套json处理器。与其他json处理器(如Gson,Jackson等)和其他的Java对象序列化反序列化方式相比,有比较明显的性能优势。详情可以参考fastjson提供的benchmark。

https://github.com/eishay/jvm-serializers/wiki

com.alibaba.fastjson.JSONObject 时经常会用到它的转换方法,包括Java对象转成JSON串、JSON对象,JSON串转成java对象、JSON对象,JSON对象转换Java对象、JSON串等,使用方法总结如下

Java对象—>JSON字符串

  1. Java对象—>JSON对象

(JSONObject)JSONObject.toJSON(Java对象实例)

  1. public class JSON2JavaTest{
  2. public static void main(String[] args) {
  3. Student stu = new Student("公众号编程大道", "m", 2);
  4. //Java对象转化为JSON对象
  5. JSONObject jsonObject = (JSONObject) JSONObject.toJSON(stu);
  6. System.out.println("Java对象转化为JSON对象\n" + jsonObject);//{"name":"公众号编程大道","age":2,"sex":"m"}
  7. }
  8. }
  1. JSON对象—>JSON字符串

JSONObject.toJSONString();

  1. public class JSON2JavaTest{
  2. public static void main(String[] args) {
  3. Student stu = new Student("公众号编程大道", "m", 2);
  4. //先转成JSON对象
  5. JSONObject jsonObject = (JSONObject) JSONObject.toJSON(stu);
  6. //JSON对象转换为JSON字符串
  7. String jsonString = jsonObject.toJSONString();
  8. System.out.println("JSON对象转换为JSON字符串\n" + jsonString);//{"name":"公众号编程大道","age":2,"sex":"m"}
  9. }
  10. }
  1. Java对象—>JSON字符串

JSONObject.toJSONString(Java对象实例)

  1. public class JSON2JavaTest{
  2. public static void main(String[] args) {
  3. Student stu = new Student("公众号编程大道", "m", 2);
  4. //Java对象转换成JSON字符串
  5. //String stuString = JSONObject.toJSONString(stu);
  6. //最好使用
  7. String stuString = JSON.toJSONString(stu);
  8. System.out.println("Java对象转换成JSON字符串\n" + stuString);//{"age":2,"name":"公众号编程大道","sex":"m"}
  9. }
  10. }

JSON字符串—>Java对象/List

  1. JSON字符串—>JSON对象

JSONObject.parseObject(JSON字符串)

  1. public class JSON2JavaTest{
  2. public static void main(String[] args) {
  3. String stuString = "{\"age\":2,\"name\":\"公众号编程大道\",\"sex\":\"m\"}";
  4. //JSON字符串转换成JSON对象
  5. JSONObject jsonObject1 = JSONObject.parseObject(stuString);
  6. System.out.println("JSON字符串转换成JSON对象\n" + jsonObject1);//{"sex":"m","name":"公众号编程大道","age":2}
  7. }
  8. }
  1. JSON对象—>Java对象

JSONObject.toJavaObject(JSON对象实例, Java对象.class);

  1. public class JSON2JavaTest{
  2. public static void main(String[] args) {
  3. Student stu = new Student("公众号编程大道", "m", 2);
  4. //先转成JSON对象
  5. JSONObject jsonObject = (JSONObject) JSONObject.toJSON(stu);
  6. //JSON对象转换成Java对象
  7. Student student = JSONObject.toJavaObject(jsonObject, Student.class);
  8. System.out.println("JSON对象转换成Java对象\n" + student);//Student{name='公众号编程大道', sex='m', age=2}
  9. }
  10. }
  1. JSON字符串—>Java对象

JSONObject.parseObject(JSON字符串, Java对象.class);

  1. public class JSON2JavaTest{
  2. public static void main(String[] args) {
  3. String stuString = "{\"age\":2,\"name\":\"公众号编程大道\",\"sex\":\"m\"}";
  4. //JSON字符串转换成Java对象
  5. Student student1 = JSONObject.parseObject(stuString, Student.class);
  6. System.out.println("JSON字符串转换成Java对象\n" + student1);//Student{name='公众号编程大道', sex='m', age=2}
  7. }
  8. }

JSON字符串—>Java ArrayList

  1. public class JSON2ArrayListTest{
  2. JSONArray jsonArray = .........;
  3. String jsonStr = JSONObject.toJSONString(jsonArray);
  4. //JSON字符串转换成Java List
  5. List<MyClass> list = JSONObject.parseArray(jsonStr, MyClass.class);
  6. for (int i=0; i<list.size(); i++) {
  7. System.out.println(list.get(i));
  8. }
  9. }

相比于net.sf.json的JSONArray转List,com.alibaba.fastjson的表现更好,它可以解决JSONArray的元素内含嵌套json的情况。

空值如何打印

当打印JSON的时候,如果Java对象的属性的值为null,那么这个值不会被打印出来,如何解决这个问题?下面直接看代码实例:
Spring Boot中的JSON技术 - 图1

这里箭头指向的位置,因为sent-1 中的value为空,所以并未打印出来。
第二个使用:JSON.toJSONString(map, SerializerFeature.WriteMapNullValue) 指定序列化方式就打印出来了。

所以大家使用的时候一定切记这里的坑。下面再看看fastJson一些默认一下属性:

JSON.toJSONString 源码:

Spring Boot中的JSON技术 - 图2

然后看下SerializerFeature属性:

名称 含义
QuoteFieldNames 输出key时是否使用双引号,默认为true
UseSingleQuotes 使用单引号而不是双引号,默认为false
WriteMapNullValue 是否输出值为null的字段,默认为false
WriteEnumUsingToString Enum输出name()或者original,默认为false
SortField 按字段名称排序后输出。默认为false
WriteTabAsSpecial 把\t做转义输出,默认为false
PrettyForma 结果是否格式化,默认为false
WriteClassName 序列化时写入类型信息,默认为false。反序列化是需用到
…… ……

看到这里大家可以针对自己的需求选择不同序列化格式,更多SerializerFeature 请大家自行查阅。

@ResponseBody+自定义ObjectMapper

我们都知道,在Spring中使用@ResponseBody注解可以将方法返回的对象序列化成JSON。注意:在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。

比如:

  1. @RequestMapping("getuser")
  2. @ResponseBody
  3. public User getUser() {
  4. User user = new User();
  5. user.setUserName("mrbird");
  6. user.setBirthday(new Date());
  7. return user;
  8. }

User类:

  1. public class User implements Serializable {
  2. private static final long serialVersionUID = 6222176558369919436L;
  3. private String userName;
  4. private int age;
  5. private String password;
  6. private Date birthday;
  7. ...
  8. }

访问getuser页面输出:

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

可看到时间默认以时间戳的形式输出,如果想要改变这个默认行为,我们可以自定义一个ObjectMapper来替代:

  1. import java.text.SimpleDateFormat;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. @Configuration
  6. public class JacksonConfig {
  7. @Bean
  8. public ObjectMapper getObjectMapper(){
  9. ObjectMapper mapper = new ObjectMapper();
  10. mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
  11. return mapper;
  12. }
  13. }

上面配置获取了ObjectMapper对象,并且设置了时间格式。再次访问getuser,页面输出:

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

Jackson JSON

平日里在项目中处理JSON一般用的都是阿里巴巴的Fastjson,后来发现使用Spring Boot内置的Jackson来完成JSON的序列化和反序列化操作也挺方便。Jackson不但可以完成简单的序列化和反序列化操作,也能实现复杂的个性化的序列化和反序列化操作。

序列化

Jackson通过使用mapper的writeValueAsString方法将Java对象序列化为JSON格式字符串:

  1. @Autowired
  2. ObjectMapper mapper;
  3. @RequestMapping("serialization")
  4. @ResponseBody
  5. public String serialization() {
  6. try {
  7. User user = new User();
  8. user.setUserName("mrbird");
  9. user.setBirthday(new Date());
  10. String str = mapper.writeValueAsString(user);
  11. return str;
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. return null;
  16. }

反序列化

使用@ResponseBody注解可以使对象序列化为JSON格式字符串,除此之外,Jackson也提供了反序列化方法。

树遍历

当采用树遍历的方式时,JSON被读入到JsonNode对象中,可以像操作XML DOM那样读取JSON。比如:

  1. @Autowired
  2. ObjectMapper mapper;
  3. @RequestMapping("readjsonstring")
  4. @ResponseBody
  5. public String readJsonString() {
  6. try {
  7. String json = "{\"name\":\"mrbird\",\"age\":26}";
  8. JsonNode node = this.mapper.readTree(json);
  9. String name = node.get("name").asText();
  10. int age = node.get("age").asInt();
  11. return name + " " + age;
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. return null;
  16. }

readTree方法可以接受一个字符串或者字节数组、文件、InputStream等, 返回JsonNode作为根节点,你可以像操作XML DOM那样操作遍历JsonNode以获取数据。

解析多级JSON例子:

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

绑定对象

我们也可以将Java对象和JSON数据进行绑定,如下所示:

  1. @Autowired
  2. ObjectMapper mapper;
  3. @RequestMapping("readjsonasobject")
  4. @ResponseBody
  5. public String readJsonAsObject() {
  6. try {
  7. String json = "{\"name\":\"mrbird\",\"age\":26}";
  8. User user = mapper.readValue(json, User.class);
  9. String name = user.getUserName();
  10. int age = user.getAge();
  11. return name + " " + age;
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. return null;
  16. }

Jackson注解

Jackson包含了一些实用的注解:

@JsonProperty

@JsonProperty,作用在属性上,用来为JSON Key指定一个别名。

  1. @JsonProperty("bth")
  2. private Date birthday;

再次访问getuser页面输出:

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

key birthday已经被替换为了bth。

@Jsonlgnore

@Jsonlgnore,作用在属性上,用来忽略此属性。

  1. @JsonIgnore
  2. private String password;

再次访问getuser页面输出:

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

password属性已被忽略。

@JsonIgnoreProperties

@JsonIgnoreProperties,忽略一组属性,作用于类上,比如JsonIgnoreProperties({ "password", "age" })

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

再次访问getuser页面输出:

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

@JsonFormat

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

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

@JsonNaming

@JsonNaming,用于指定一个命名策略,作用于类或者属性上。Jackson自带了多种命名策略,你可以实现自己的命名策略,比如输出的key 由Java命名方式转为下面线命名方法 —— userName转化为user-name。

  1. @JsonNaming(PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy.class)
  2. public class User implements Serializable {
  3. ...
  4. }

再次访问getuser页面输出:

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

@JsonSerialize

@JsonSerialize,指定一个实现类来自定义序列化。类必须实现JsonSerializer接口,代码如下:

  1. import java.io.IOException;
  2. import com.example.pojo.User;
  3. import com.fasterxml.jackson.core.JsonGenerator;
  4. import com.fasterxml.jackson.core.JsonProcessingException;
  5. import com.fasterxml.jackson.databind.JsonSerializer;
  6. import com.fasterxml.jackson.databind.SerializerProvider;
  7. public class UserSerializer extends JsonSerializer<User> {
  8. @Override
  9. public void serialize(User user, JsonGenerator generator, SerializerProvider provider)
  10. throws IOException, JsonProcessingException {
  11. generator.writeStartObject();
  12. generator.writeStringField("user-name", user.getUserName());
  13. generator.writeEndObject();
  14. }
  15. }

上面的代码中我们仅仅序列化userName属性,且输出的key是user-name。 使用注解@JsonSerialize来指定User对象的序列化方式:

  1. @JsonSerialize(using = UserSerializer.class)
  2. public class User implements Serializable {
  3. ...
  4. }

再次访问getuser页面输出:

  1. {"user-name":"mrbird"}

@JsonDeserialize

@JsonDeserialize,用户自定义反序列化,同@JsonSerialize ,类需要实现JsonDeserializer接口。

  1. import java.io.IOException;
  2. import com.example.pojo.User;
  3. import com.fasterxml.jackson.core.JsonParser;
  4. import com.fasterxml.jackson.core.JsonProcessingException;
  5. import com.fasterxml.jackson.databind.DeserializationContext;
  6. import com.fasterxml.jackson.databind.JsonDeserializer;
  7. import com.fasterxml.jackson.databind.JsonNode;
  8. public class UserDeserializer extends JsonDeserializer<User> {
  9. @Override
  10. public User deserialize(JsonParser parser, DeserializationContext context)
  11. throws IOException, JsonProcessingException {
  12. JsonNode node = parser.getCodec().readTree(parser);
  13. String userName = node.get("user-name").asText();
  14. User user = new User();
  15. user.setUserName(userName);
  16. return user;
  17. }
  18. }

使用注解@JsonDeserialize来指定User对象的序列化方式:

  1. @JsonDeserialize (using = UserDeserializer.class)
  2. public class User implements Serializable {
  3. ...
  4. }

测试:

  1. @Autowired
  2. ObjectMapper mapper;
  3. @RequestMapping("readjsonasobject")
  4. @ResponseBody
  5. public String readJsonAsObject() {
  6. try {
  7. String json = "{\"user-name\":\"mrbird\"}";
  8. User user = mapper.readValue(json, User.class);
  9. String name = user.getUserName();
  10. return name;
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. return null;
  15. }

访问readjsonasobject,页面输出:

  1. mrbird

@JsonView

@JsonView,作用在类或者属性上,用来定义一个序列化组。 比如对于User对象,某些情况下只返回userName属性就行,而某些情况下需要返回全部属性。 因此User对象可以定义成如下:

  1. public class User implements Serializable {
  2. private static final long serialVersionUID = 6222176558369919436L;
  3. public interface UserNameView {};
  4. public interface AllUserFieldView extends UserNameView {};
  5. @JsonView(UserNameView.class)
  6. private String userName;
  7. @JsonView(AllUserFieldView.class)
  8. private int age;
  9. @JsonView(AllUserFieldView.class)
  10. private String password;
  11. @JsonView(AllUserFieldView.class)
  12. private Date birthday;
  13. ...
  14. }

User定义了两个接口类,一个为userNameView,另外一个为AllUserFieldView继承了userNameView接口。这两个接口代表了两个序列化组的名称。属性userName使用了@JsonView(UserNameView.class),而剩下属性使用了@JsonView(AllUserFieldView.class)

Spring中Controller方法允许使用@JsonView指定一个组名,被序列化的对象只有在这个组的属性才会被序列化,代码如下:

  1. @JsonView(User.UserNameView.class)
  2. @RequestMapping("getuser")
  3. @ResponseBody
  4. public User getUser() {
  5. User user = new User();
  6. user.setUserName("mrbird");
  7. user.setAge(26);
  8. user.setPassword("123456");
  9. user.setBirthday(new Date());
  10. return user;
  11. }

访问getuser页面输出:

  1. {"userName":"mrbird"}

如果将@JsonView(User.UserNameView.class)替换为@JsonView(User.AllUserFieldView.class),输出:

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

因为接口AllUserFieldView继承了接口UserNameView所以userName也会被输出。

集合的反序列化

在Controller方法中,可以使用@RequestBody将提交的JSON自动映射到方法参数上,比如:

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

上面方法可以接受如下一个JSON请求,并自动映射到User对象上:

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

Spring Boot 能自动识别出List对象包含的是User类,因为在方法中定义的泛型的类型会被保留在字节码中,所以Spring Boot能识别List包含的泛型类型从而能正确反序列化。

有些情况下,集合对象并没有包含泛型定义,如下代码所示,反序列化并不能得到期望的结果。

  1. @Autowired
  2. ObjectMapper mapper;
  3. @RequestMapping("customize")
  4. @ResponseBody
  5. public String customize() throws JsonParseException, JsonMappingException, IOException {
  6. String jsonStr = "[{\"userName\":\"mrbird\",\"age\":26},{\"userName\":\"scott\",\"age\":27}]";
  7. List<User> list = mapper.readValue(jsonStr, List.class);
  8. String msg = "";
  9. for (User user : list) {
  10. msg += user.getUserName();
  11. }
  12. return msg;
  13. }

访问customize,控制台抛出异常:

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

这是因为在运行时刻,泛型己经被擦除了(不同于方法参数定义的泛型,不会被擦除)。为了提供泛型信息,Jackson提供了JavaType ,用来指明集合类型,将上述方法改为:

  1. @Autowired
  2. ObjectMapper mapper;
  3. @RequestMapping("customize")
  4. @ResponseBody
  5. public String customize() throws JsonParseException, JsonMappingException, IOException {
  6. String jsonStr = "[{\"userName\":\"mrbird\",\"age\":26},{\"userName\":\"scott\",\"age\":27}]";
  7. JavaType type = mapper.getTypeFactory().constructParametricType(List.class, User.class);
  8. List<User> list = mapper.readValue(jsonStr, type);
  9. String msg = "";
  10. for (User user : list) {
  11. msg += user.getUserName();
  12. }
  13. return msg;
  14. }

访问customize,页面输出:mrbirdscott