使用 Jackson 操作 JSON 数据


1. 工具简介

Jackson 项目 GitHub 主页

Baeldung 上关于 JSON 的使用

涉及到一些不常见的用法或功能,可以到以上两个地址查看,网上博客类的资料相对较少。

Jackson核心模块是所有扩展的基础。目前有3个核心模块(从Jackson 2.x开始):

  • Streaming:(jackson-core)定义了低级流API,包括JSON特性的实现。
  • Annotations:(jackson-annotations)包含标准的Jackson注解。
  • Databind:(jackson-databind)实现了数据绑定(和对象序列化)支持,它依赖于StreamingAnnotations包。
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>${jackson.version.core}</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>${jackson-annotations-version}</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jackson.version}</version>
  </dependency>

SpringBoot中使用的话引入web依赖,就直接引入了Jackson

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

依赖如下:

2. 简单使用

Jackson提供了三种JSON的处理方式。分别是数据绑定JSON树模型流式API。下面分别介绍这三种方式。

2.1. 数据绑定

import lombok.Data;
@Data
public class Student {
    private Long id;
    private String name;
    private Integer age;
    private String sex;
    private String[] interest;
}
public class Test {
    public static void main(String[] args) throws IOException {
        Student student = new Student();
        student.setId(1L);
        student.setName("zhangsan");
        student.setAge(20);
        student.setInterest(new String[]{"music", "coding"});

        ObjectMapper mapper = new ObjectMapper();
        //测试代码......
    }
}
2.1.1. JavaBean 转 JSON 字符串
String studentStr = mapper.writeValueAsString(student);
System.out.println(studentStr);
//{"id":1,"name":"zhangsan","age":20,"sex":null,"interest":["music","coding"]}
2.1.2. JSON 字符串转 JavaBean
Student stu = mapper.readValue(studentStr, Student.class);
System.out.println(stu);
//Student(id=1, name=zhangsan, age=20, sex=null, interest=[music, coding])
2.1.3. JSON 字符串转 Map 集合
//对泛型的反序列化,使用TypeReference可以明确的指定反序列化的类型。
//import com.fasterxml.jackson.core.type.TypeReference;
Map<String, Object> map = mapper.readValue(studentStr, new TypeReference<Map<String, Object>>(){});
System.out.println(map);
//{id=1, name=zhangsan, age=20, sex=null, interest=[music, coding]}
2.1.4. JavaBean 写入到文件
//写到文件
mapper.writeValue(new File("/json.txt"), student);
//从文件中读取
Student student1 = mapper.readValue(new File("/json.txt"), Student.class);
System.out.println(student1);
//Student(id=1, name=zhangsan, age=20, sex=null, interest=[music, coding])
2.1.5. JavaBean 转字节数组
//写为字节流
byte[] bytes = mapper.writeValueAsBytes(student);
//从字节流读取
Student student2 = mapper.readValue(bytes, Student.class);
System.out.println(student2);
//Student(id=1, name=zhangsan, age=20, sex=null, interest=[music, coding])

2.2. JSON 树模型

Jackson树模型结构,可以通过pathgetJsonPointer等进行操作,适合用来获取大JSON中的字段,比较灵活。缺点是如果需要获取的内容较多,会显得比较繁琐。

2.2.1. 构建 JSON 树模型
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
public class Test {
    public static void main(String[] args) throws IOException {
        //构建JSON树
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode root = mapper.createObjectNode();
        root.put("id", 1L);
        root.put("name", "zhangsan");
        root.put("age", 20);
        ArrayNode interest = root.putArray("interest");
        interest.add("music");
        interest.add("coding");
        //测试代码......
    }
}
2.2.2. JSON 树转 JSON 字符串
String json = mapper.writeValueAsString(root);
System.out.println(json);
//{"id":1,"name":"zhangsan","age":20,"interest":["music","coding"]}
2.2.3. 解析 JSON 树模型
//将JSON字符串转为JSON树
JsonNode rootNode = mapper.readTree(json);
//解析值,使用path或者get
Long id = rootNode.path("id").asLong();
System.out.println(id);//1
String name = rootNode.path("name").asText();
System.out.println(name);//zhangsan
Integer age = rootNode.get("age").asInt();
System.out.println(age);//20
//解析数组
JsonNode arrayNode = rootNode.get("interest");
if (arrayNode.isArray()){
    for (JsonNode jsonNode : arrayNode){
        System.out.println(jsonNode.asText());
        //music
        //coding
    }
}

pathget方法看起来很相似,其实它们的细节不同。

path方法取不存在的值的时候会返回一个"missing node",该"missing node"isMissingNode方法返回值为true,如果调用该nodeasText方法的话,结果是一个空字符串。

System.out.println(rootNode.path("notExist").isMissingNode());//true
System.out.println(rootNode.path("notExist").asText());//空串

get方法取不存在的值的时候,直接会返回null

System.out.println(rootNode.get("notExist") == null);//true
System.out.println(rootNode.get("notExist"));//null

key存在,而valuenull的时候,getpath都会返回一个NullNode节点。该节点的asText方法返回null字符串。

String s = "{\"nullNode\":null}";
JsonNode jsonNode = mapper.readTree(s);
System.out.println(jsonNode.get("nullNode") instanceof NullNode);//true
System.out.println(jsonNode.get("nullNode"));//null
System.out.println(jsonNode.path("nullNode") instanceof NullNode);//true
System.out.println(jsonNode.path("nullNode"));//null

使用JsonPointer解析JSON树。

String s1 = "{\"obj\": {\"name\": \"wang\",\"class\": \"3\"}}";
JsonNode jsonNode1 = mapper.readTree(s1);
JsonPointer jsonPointer = JsonPointer.valueOf("/obj/name");
JsonNode node = jsonNode1.at(jsonPointer);
System.out.println(node.asText());//wang

2.3. 流式 API

参考地址:JackSon的几种用法

流式API是一套比较底层的API,速度快,但是使用起来特别麻烦。它主要是有两个核心类,一个是JsonGenerator,用来生成JSON,另一个是JsonParser,用来读取JSON内容。

import com.fasterxml.jackson.core.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Test {
    public static void main(String[] args) throws IOException {
        JsonFactory factory = new JsonFactory();
        String s = "{\"id\": 1,\"name\": \"小明\",\"array\": [\"1\", \"2\"]," +
                "\"test\":\"I'm test\",\"nullNode\":null,\"base\": {\"major\": \"物联网\",\"class\": \"3\"}}";

        //这里就举一个比较简单的例子,Generator的用法就是一个一个write即可。
        File file = new File("/json.txt");
        JsonGenerator jsonGenerator = factory.createGenerator(file, JsonEncoding.UTF8);
        //对象开始
        jsonGenerator.writeStartObject();
        //写入一个键值对
        jsonGenerator.writeStringField("name", "小光");
        //对象结束
        jsonGenerator.writeEndObject();
        //关闭jsonGenerator
        jsonGenerator.close();
        //读取刚刚写入的json
        FileInputStream inputStream = new FileInputStream(file);
        int i = 0;
        final int SIZE = 1024;
        byte[] buf = new byte[SIZE];
        StringBuilder sb = new StringBuilder();
        while ((i = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf,0,i));
        }
        inputStream.close();

        //JsonParser解析的时候,思路是把json字符串根据边界符分割为若干个JsonToken,这个JsonToken是一个枚举类型。
        //下面这个小例子,可以看出JsonToken是如何划分类型的。
        JsonParser parser = factory.createParser(s);
        while (!parser.isClosed()){
            JsonToken token = parser.currentToken();
            System.out.println(token);
            parser.nextToken();
        }
        JsonParser jsonParser = factory.createParser(s);
        //下面是一个解析的实例
        while (!jsonParser.isClosed()) {
            JsonToken token  = jsonParser.nextToken();
            if (JsonToken.FIELD_NAME.equals(token)) {
                String currentName = jsonParser.currentName();
                token = jsonParser.nextToken();
                if ("id".equals(currentName)) {
                    System.out.println("id:" + jsonParser.getValueAsInt());
                } else if ("name".equals(currentName)) {
                    System.out.println("name:" + jsonParser.getValueAsString());
                } else if ("array".equals(currentName)) {
                    token = jsonParser.nextToken();
                    while (!JsonToken.END_ARRAY.equals(token)) {
                        System.out.println("array:" + jsonParser.getValueAsString());
                        token = jsonParser.nextToken();
                    }
                }
            }
        }
    }
}

3. 注解使用

参考地址:注解文档

3.1. @JsonProperty

使用在JavaBean的字段上,指定一个字段用于JSON映射,默认情况下映射的JSON字段与注解的字段名称相同。该注解有三个属性:

  • value:用于指定映射的JSON的字段名称。常用。
  • index:用于指定映射的JSON的字段顺序。
  • defaultValue:定义为元数据的文本默认值。注意:core databind不使用该属性,它目前只公开给扩展模块使用。
@JsonProperty(value = “user_name”)

3.2. @JsonIgnore

可用于字段、getter/setter、构造函数参数上,作用相同,都会对相应的字段产生影响。使相应字段不参与序列化和反序列化,也就是说,向 getter 添加该注解会禁用 setter。除非 setter 上有@JsonProperty注解,在这种情况下,这被认为是一个“分割属性”,启用了“setter”,但没有“getter”(“只读”,因此属性可以从输入读取,但不是写输出)。

3.3. @JsonIgnoreProperties

该注解是类注解。该注解在 Java 类和 JSON 不完全匹配的时候使用。

  • 在序列化为 JSON 的时候,@JsonIgnoreProperties({"prop1", "prop2"})会忽略 pro1 和 pro2 两个属性。
  • 在从 JSON 反序列化为 Java 类的时候,@JsonIgnoreProperties(ignoreUnknown=true)会忽略所有没有 getter 和 setter 的属性,也就是忽略类中不存在的字段。

3.4. @JsonIgnoreType

该注解是类注解,序列化为 JSON 的时候会排除所有指定类型的字段。

3.5. @JsonInclude

用于定义在序列化时是否不应包含某些“非值”(null 值或空值)的注解。可以用于每个字段上,也可以用于类上(表示用于类的所有属性)。

//忽略类中值为null的字段
@JsonInclude(value = JsonInclude.Include.NON_NULL)
//忽略类中值为空的字段。对于字符串,即忽略null或空字符串
@JsonInclude(Include.NON_EMPTY)

3.6. @JsonFormat

用于字段上,预期类型行为的通用注释;例如,可以用来指定序列化日期/时间值时使用的格式。

java.util.Date使用如下,java.sql.Date类似。(注意时区问题,这里添加了timezone = "GMT+8"

import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
public class DateModel {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date date;

    public Date getDate() {return date;}
    public void setDate(Date date) {this.date = date;}
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Date;
public class Test {
    public static void main(String[] args) throws JsonProcessingException {
        DateModel dateModel = new DateModel();
        dateModel.setDate(new Date());
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writeValueAsString(dateModel));
        //{"date":"2020-01-01 12:16:54"}
    }
}

但是注意如果JavaBean中的时间字段使用的是JDK8新增的时间日期(LocalDate / LocalTime / LocalDateTime)字段的话,直接这样使用是不起作用的。我们需要添加其他匹配,具体可参考GitHub上的说明:Jackson格式化JDK8日期

  • 添加jackson-datatype-jsr310maven配置,SpringBootweb模块会自动引入。
  • 需要进行模块注册。具体看下面的示例代码。
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
public class DateModel {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime date;

    public LocalDateTime getDate() {return date;}
    public void setDate(LocalDateTime date) {this.date = date;}
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDateTime;
public class Test {
    public static void main(String[] args) throws JsonProcessingException {
        DateModel dateModel = new DateModel();
        dateModel.setDate(LocalDateTime.now());
        ObjectMapper mapper = new ObjectMapper();
        //自动发现并注册模块
        mapper.findAndRegisterModules();
        System.out.println(mapper.writeValueAsString(dateModel));
        //{"date":"2020-01-01 12:40:08"}
    }
}

3.7. @JsonPropertyOrder

@JsonPropertyindex属性类似,指定属性序列化时的顺序。

3.8. @JsonRootName

类注解。用于指定JSON根属性的名称。生成的JSON如下所示:

{"Teacher":{"id":2,"name":"wangwu","age":35}}

示例代码:

@JsonRootName("Teacher")
public class Teacher {
    private Long id;
    private String name;
    private Integer age;
    @JsonIgnore//转换为JSON时不需要的字段,用在属性上。
    private String sex;

    //省略Setter/Getter方法

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}
Teacher teacher = new Teacher();
teacher.setId(2L);
teacher.setName("wangwu");
teacher.setAge(35);
teacher.setSex("男");

ObjectMapper mapper = new ObjectMapper();
//开启包装根值的配置
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);

//将Java对象转换为JSON字符串
String teacherStr = mapper.writeValueAsString(teacher);
System.out.println(teacherStr);
//{"Teacher":{"id":2,"name":"wangwu","age":35}}

//开启了根包装之后,生成的json字符串和java类不对应了,
//所以在反序列化为java类的时候会报错,关闭该属性不会报错,但是值会为空
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

//将JSON字符串转换为Java对象
Teacher tea = mapper.readValue(teacherStr, Teacher.class);
System.out.println(tea);
//Teacher{id=null, name='null', age=null, sex='null'}

3.9. @JsonAnySetter 和 @JsonAnyGetter

这两个属性是用来在序列化和反序列化的时候多余字段可以通过Map来回转换。也就是JSON中的字段比对应的JavaBean中的字段多,可以在JavaBean中使用一个Map字段来接收多余的JSON字段。

3.9.1. @JsonAnyGetter
  1. 用在非静态方法上,没有参数,方法名随意(可以直接写在Getter方法上)。
  2. 方法返回值必须是Map类型。
  3. 在一个实体类中仅仅用在一个方法上。
  4. 序列化的时候JSON字段的key就是返回Mapkeyvalue就是Mapvalue
3.9.2. @JsonAnySetter
  1. 用在非静态方法上,注解的方法必须有两个参数,第一个是JSON字段中的key,第二个是value,方法名随意(注意这个方法不是Setter方法)。
  2. 也可以用在Map对象属性上面,建议用在Map对象属性上面。
  3. 反序列化的时候将对应不上的字段全部放到Map里面。
3.9.3. 示例代码
import com.fasterxml.jackson.annotation.*;
import lombok.Data;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
 * @author wangbo
 * @date 2019/11/16 10:47
 */
@Data
public class Student {
    private Long id;
    private String name;
    private Integer age;
    //自定义字段
    private Map<String, Object> other = new HashMap();

    @JsonAnyGetter
    public Map<String, Object> getOther() {
        return other;
    }

    @JsonAnySetter
    public void setOther(String key, Object value) {
        this.other.put(key, value);
    }
}
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author wangbo
 * @date 2019/11/16 16:54
 */
public class Test {
    public static void main(String[] args) throws IOException {
        Map<String,Object> map = new HashMap<>();
        map.put("id", 1L);
        map.put("name", "菲菲");
        map.put("age", 20);
        map.put("score", 90);
        map.put("sex", "女");

        ObjectMapper mapper = new ObjectMapper();
        String s = mapper.writeValueAsString(map);//序列化
        System.out.println(s);
        //{"score":90,"sex":"女","name":"菲菲","id":1,"age":20}

        Student student = mapper.readValue(s, Student.class);//反序列化
        System.out.println(student);
        //Student(id=1, name=菲菲, age=20, other={score=90, sex=女})
        String s1 = mapper.writeValueAsString(student);//序列化
        System.out.println(s1);
        //{"id":1,"name":"菲菲","age":20,"score":90,"sex":"女"}
    }
}

3.10. @JsonNaming

该注解放在类上。序列化的时候该注解可将驼峰命名的字段名转换为下划线分隔的小写字母命名方式的key。反序列化的时候可以将下划线分隔的小写字母key转换为驼峰命名的字段名。

@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)

代码示例:

import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;
/**
 * @author wangbo
 * @date 2019/11/16 10:47
 */
@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Student {
    private Long appId;
    private String nickName;
    private Integer nowAge;
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
/**
 * @author wangbo
 * @date 2019/11/16 16:54
 */
public class Test {
    public static void main(String[] args) throws IOException {
        Student student = new Student();
        student.setAppId(1L);
        student.setNickName("zhangsan");
        student.setNowAge(20);

        ObjectMapper mapper = new ObjectMapper();
        String s = mapper.writeValueAsString(student);//序列化
        System.out.println(s);
        //{"app_id":1,"nick_name":"zhangsan","now_age":20}
        Student student1 = mapper.readValue(s, Student.class);//反序列化
        System.out.println(student1);
        //Student(appId=1, nickName=zhangsan, nowAge=20)
    }
}

4. Jackson 配置

这里有三个方法,configure方法接受配置名和要设置的值,Jackson 2.5版本新加的enabledisable方法则直接启用和禁用相应属性,推荐使用后面两个方法。

// 美化输出
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// 强制JSON空字符串("")转换为null对象值
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

// 允许序列化空的POJO类(否则会抛出异常)
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 把java.util.Date, Calendar输出为数字(时间戳)
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 在遇到未知属性的时候不抛出异常
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

// 在JSON中允许C/C++ 样式的注释(非标准,默认禁用)
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
// 允许没有引号的字段名(非标准)
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 允许单引号(非标准)
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// 强制转义非ASCII字符
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
// 将内容包裹为一个JSON属性,属性名由@JsonRootName注解指定
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值