16、Java中使用Gson和JsonPath处理JSON数据

Java中使用Gson和JsonPath处理JSON数据

1. Gson处理JSON数据

Gson是一个基于Java的小型库,用于解析和创建JSON对象。它通过Java的反射API将JSON对象反序列化为Java对象,也能将Java对象序列化为JSON对象。Gson由超过30个类和接口组成,分布在四个包中:
- com.google.gson :提供对主类 Gson 的访问。
- com.google.gson.annotations :提供与Gson一起使用的注解类型。
- com.google.gson.reflect :提供一个实用类,用于从泛型类型中获取类型信息。
- com.google.gson.stream :提供用于读写JSON编码值的实用类。

1.1 泛型类型处理问题及解决

在处理泛型类型时,可能会遇到一些问题。例如,当指定 Vehicle 而不是完整的 Vehicle<Truck> 泛型类型时, Vehicle 类中的 vehicle 字段会被赋值为 com.google.gson.internal.LinkedTreeMap ,而不是 Truck 类型。另外,在序列化和反序列化基于 java.util.HashMap 匿名子类的映射时, toJson() 可能会失败, fromJson() 也可能因传入 null 而返回 null

解决方法是创建 TypeToken<Vehicle<Truck>> TypeToken<Map<String,String>> 对象来存储参数化类型,然后将这些对象传递给 toJson(Object src, Type typeOfSrc) <T> T fromJson( String json, Type typeOfT) 方法的 type 参数。示例代码如下:

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

public class GenericTypeDemo {
    public static void main(String[] args) {
        Gson gson = new Gson();
        Map<String, String> map = new HashMap<>();
        map.put("key", "value");

        Type type = new TypeToken<Map<String, String>>() {}.getType();
        String json = gson.toJson(map, type);
        Map<String, String> newMap = gson.fromJson(json, type);
    }
}
1.2 TypeAdapter的使用

JsonSerializer JsonDeserializer 可以将Java对象序列化为JSON字符串,反之亦然,但会增加中间处理层,影响性能。可以使用 com.google.gson.TypeAdapter<T> 类来避免中间层,创建更高效的代码。

TypeAdapter 是一个抽象类,声明了几个具体方法和一对抽象方法:
- T read(JsonReader in) :读取JSON值并将其转换为Java对象。
- void write(JsonWriter out, T value) :将Java对象写入JSON值。

以下是一个使用 TypeAdapter 序列化和反序列化 Country 对象的示例:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

public class GsonDemo {
    static class Country {
        String name;
        int population;
        String[] cities;
        Country() {}
        Country(String name, int population, String... cities) {
            this.name = name;
            this.population = population;
            this.cities = cities;
        }
    }

    public static void main(String[] args) {
        class CountryAdapter extends TypeAdapter<Country> {
            @Override
            public Country read(JsonReader in) throws IOException {
                Country c = new Country();
                List<String> cities = new ArrayList<>();
                in.beginObject();
                while (in.hasNext()) {
                    switch (in.nextName()) {
                        case "name":
                            c.name = in.nextString();
                            break;
                        case "population":
                            c.population = in.nextInt();
                            break;
                        case "cities":
                            in.beginArray();
                            while (in.hasNext()) {
                                cities.add(in.nextString());
                            }
                            in.endArray();
                            c.cities = cities.toArray(new String[0]);
                            break;
                    }
                }
                in.endObject();
                return c;
            }

            @Override
            public void write(JsonWriter out, Country c) throws IOException {
                out.beginObject();
                out.name("name").value(c.name);
                out.name("population").value(c.population);
                out.name("cities");
                out.beginArray();
                for (int i = 0; i < c.cities.length; i++) {
                    out.value(c.cities[i]);
                }
                out.endArray();
                out.endObject();
            }
        }

        Gson gson = new GsonBuilder()
                .registerTypeAdapter(Country.class, new CountryAdapter())
                .create();
        Country c = new Country("England", 53012456, "London", "Birmingham", "Cambridge");
        String json = gson.toJson(c);
        System.out.println(json);
        c = gson.fromJson(json, c.getClass());
        System.out.printf("Name = %s%n", c.name);
        System.out.printf("Population = %d%n", c.population);
        System.out.print("Cities = ");
        for (String city : c.cities) {
            System.out.print(city + " ");
        }
        System.out.println();
    }
}

上述代码的执行流程如下:
1. 定义 Country 类,包含国家名称、人口数量和城市数组。
2. 创建 CountryAdapter 类,继承自 TypeAdapter<Country> ,重写 read write 方法。
- read 方法从 JsonReader 中读取JSON数据,将其转换为 Country 对象。
- write 方法将 Country 对象写入 JsonWriter 生成JSON数据。
3. 使用 GsonBuilder 注册 CountryAdapter ,创建 Gson 对象。
4. 创建 Country 对象,进行序列化和反序列化操作,并输出结果。

1.3 JsonAdapter注解的使用

JsonAdapter 注解类型可用于将 TypeAdapter 实例与类或字段关联起来。使用该注解后,无需再使用 GsonBuilder registerTypeAdapter 方法注册 TypeAdapter ,减少了代码量。以下是示例代码:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

public class GsonDemo {
    @JsonAdapter(CountryAdapter.class)
    static class Country {
        String name;
        int population;
        String[] cities;
        Country() {}
        Country(String name, int population, String... cities) {
            this.name = name;
            this.population = population;
            this.cities = cities;
        }
    }

    static class CountryAdapter extends TypeAdapter<Country> {
        @Override
        public Country read(JsonReader in) throws IOException {
            System.out.println("read() called");
            Country c = new Country();
            List<String> cities = new ArrayList<>();
            in.beginObject();
            while (in.hasNext()) {
                switch (in.nextName()) {
                    case "name":
                        c.name = in.nextString();
                        break;
                    case "population":
                        c.population = in.nextInt();
                        break;
                    case "cities":
                        in.beginArray();
                        while (in.hasNext()) {
                            cities.add(in.nextString());
                        }
                        in.endArray();
                        c.cities = cities.toArray(new String[0]);
                        break;
                }
            }
            in.endObject();
            return c;
        }

        @Override
        public void write(JsonWriter out, Country c) throws IOException {
            System.out.println("write() called");
            out.beginObject();
            out.name("name").value(c.name);
            out.name("population").value(c.population);
            out.name("cities");
            out.beginArray();
            for (int i = 0; i < c.cities.length; i++) {
                out.value(c.cities[i]);
            }
            out.endArray();
            out.endObject();
        }
    }

    public static void main(String[] args) {
        Gson gson = new Gson();
        Country c = new Country("England", 53012456, "London", "Birmingham", "Cambridge");
        String json = gson.toJson(c);
        System.out.println(json);
        c = gson.fromJson(json, c.getClass());
        System.out.printf("Name = %s%n", c.name);
        System.out.printf("Population = %d%n", c.population);
        System.out.print("Cities = ");
        for (String city : c.cities) {
            System.out.print(city + " ");
        }
        System.out.println();
    }
}

上述代码的执行流程如下:
1. 在 Country 类上使用 @JsonAdapter(CountryAdapter.class) 注解,关联 CountryAdapter
2. 创建 CountryAdapter 类,重写 read write 方法。
3. 创建 Gson 对象,进行 Country 对象的序列化和反序列化操作,并输出结果。

2. JsonPath提取JSON值

JsonPath是一种声明性查询语言,用于选择和提取JSON文档的属性值,它基于XPath 1.0。以下是JsonPath的一些基本信息:
- 创建者 :Stefan Goessner,他还创建了基于JavaScript和PHP的JsonPath实现。
- Java实现 :由瑞典软件公司Jayway将JsonPath适配到Java,完整文档可查看 https://github.com/jayway/JsonPath

2.1 JsonPath表达式语法

JsonPath表达式以美元符号 $ 开头,表示查询的根元素,后面可以使用点号 . 或方括号 [] 来分隔子元素。例如,对于以下JSON对象:

{
    "firstName": "John",
    "lastName": "Smith",
    "age": 25,
    "address": {
        "streetAddress": "21 2nd Street",
        "city": "New York",
        "state": "NY",
        "postalCode": "10021-3100"
    },
    "phoneNumbers": [
        {
            "type": "home",
            "number": "212 555-1234"
        },
        {
            "type": "office",
            "number": "646 555-4567"
        }
    ]
}

可以使用以下表达式提取第一个电话号码:
- 点号表示法: $.phoneNumbers[0].number
- 方括号表示法: $['phoneNumbers'][0]['number']

2.2 JsonPath基本运算符
运算符 描述
$ 查询的根元素,所有路径表达式都以它开头,相当于XPath的 / 符号。
@ 当前由过滤谓词处理的节点,相当于XPath的 . 符号。
* 通配符,可在需要名称或数值的任何位置使用。
.. 深度扫描(也称为递归下降),可在需要名称的任何位置使用,相当于XPath的 // 符号。
. name 点号表示的子元素,点号相当于XPath的 / 符号。
[' name ' (, ' name ')] 方括号表示的子元素或多个子元素。
[ number (, number )] 数组索引或多个索引。
[ start : end ] 数组切片运算符。
[?( expression )] 过滤运算符, expression 必须计算为布尔值,即谓词。
2.3 JsonPath函数
函数 描述
min() 返回数字数组中的最小值(作为 double 类型)。
max() 返回数字数组中的最大值(作为 double 类型)。
avg() 返回数字数组的平均值(作为 double 类型)。
stddev() 返回数字数组的标准差(作为 double 类型)。
length() 返回数组的长度(作为 int 类型)。
2.4 JsonPath过滤运算符
运算符 描述
== 当左操作数等于右操作数时返回 true ,注意数字和字符串不相等,如 1 不等于 '1'
!= 当左操作数不等于右操作数时返回 true
< 当左操作数小于右操作数时返回 true
<= 当左操作数小于或等于右操作数时返回 true
> 当左操作数大于右操作数时返回 true
>= 当左操作数大于或等于右操作数时返回 true
=~ 当左操作数匹配右操作数指定的正则表达式时返回 true ,例如 [?(@.name =~ /foo.*?/i)]
In 当左操作数存在于右操作数中时返回 true ,例如 [?(@.grade in ['A', 'B'])]
Nin 当左操作数不存在于右操作数中时返回 true

可以使用逻辑与运算符 && 和逻辑或运算符 || 创建更复杂的谓词,并且在谓词中,字符串字面量必须用单引号或双引号括起来。

Java中使用Gson和JsonPath处理JSON数据

3. Gson与JsonPath结合使用示例

在实际开发中,我们可能会先使用Gson将Java对象序列化为JSON字符串,然后使用JsonPath从JSON字符串中提取所需的值。以下是一个结合使用Gson和JsonPath的示例:

import com.google.gson.Gson;
import com.jayway.jsonpath.JsonPath;

import java.util.HashMap;
import java.util.Map;

public class GsonJsonPathDemo {
    public static void main(String[] args) {
        // 创建一个Java对象
        Map<String, Object> person = new HashMap<>();
        person.put("name", "John");
        person.put("age", 30);

        Map<String, String> address = new HashMap<>();
        address.put("street", "123 Main St");
        address.put("city", "New York");
        person.put("address", address);

        // 使用Gson将Java对象序列化为JSON字符串
        Gson gson = new Gson();
        String json = gson.toJson(person);

        // 使用JsonPath从JSON字符串中提取值
        String name = JsonPath.read(json, "$.name");
        int age = JsonPath.read(json, "$.age");
        String street = JsonPath.read(json, "$.address.street");
        String city = JsonPath.read(json, "$.address.city");

        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
        System.out.println("Street: " + street);
        System.out.println("City: " + city);
    }
}

操作步骤如下
1. 创建Java对象 :使用 Map 来模拟一个包含个人信息的Java对象,其中包含 name age address 等属性。
2. 使用Gson进行序列化 :创建 Gson 对象,调用 toJson 方法将Java对象转换为JSON字符串。
3. 使用JsonPath提取值 :调用 JsonPath.read 方法,传入JSON字符串和相应的JsonPath表达式,提取所需的值。
4. 输出结果 :将提取的值输出到控制台。

4. 总结与注意事项
4.1 Gson总结
  • 功能强大 :Gson可以方便地实现Java对象与JSON字符串之间的相互转换,支持泛型、注解等特性。
  • 自定义处理 :通过 JsonDeserializer JsonSerializer TypeAdapter 等接口,可以实现自定义的序列化和反序列化逻辑。
  • 性能优化 :使用 TypeAdapter 可以避免中间处理层,提高性能。
  • 注解简化 JsonAdapter 注解可以简化 TypeAdapter 的注册过程。

注意事项
- 在处理泛型类型时,需要使用 TypeToken 来指定具体的类型信息。
- 当使用自定义的序列化和反序列化逻辑时,要确保逻辑的正确性,避免出现异常。

4.2 JsonPath总结
  • 灵活查询 :JsonPath提供了丰富的表达式语法和运算符,可以灵活地从JSON文档中提取所需的值。
  • 函数支持 :支持 min max avg 等函数,方便对数组进行统计操作。
  • 过滤功能 :通过过滤运算符和谓词,可以实现复杂的过滤逻辑。

注意事项
- 在编写JsonPath表达式时,要注意运算符的使用和表达式的正确性,避免出现语法错误。
- 对于复杂的JSON结构,可能需要多次尝试和调试才能得到正确的表达式。

5. 常见问题及解决方案
5.1 Gson相关问题
  • 泛型类型处理失败 :如果在处理泛型类型时出现问题,如 vehicle 字段被赋值为 LinkedTreeMap 而不是预期的类型,可使用 TypeToken 来指定具体的类型信息,示例如下:
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

class GenericExample<T> {
    private List<T> list;

    public GenericExample() {
        this.list = new ArrayList<>();
    }

    public void add(T item) {
        list.add(item);
    }

    public List<T> getList() {
        return list;
    }
}

public class GenericTypeIssueSolution {
    public static void main(String[] args) {
        GenericExample<String> example = new GenericExample<>();
        example.add("Hello");
        example.add("World");

        Gson gson = new Gson();
        Type type = new TypeToken<GenericExample<String>>() {}.getType();
        String json = gson.toJson(example, type);

        GenericExample<String> newExample = gson.fromJson(json, type);
        System.out.println(newExample.getList());
    }
}
  • 序列化和反序列化性能问题 :如果性能是关键因素,可以使用 TypeAdapter 来避免中间处理层,提高性能,具体示例可参考前面的 CountryAdapter 代码。
5.2 JsonPath相关问题
  • 表达式语法错误 :如果JsonPath表达式返回的结果不符合预期,可能是表达式语法有误。可以仔细检查运算符的使用和路径的正确性,例如:
{
    "students": [
        {
            "name": "Alice",
            "grades": [80, 90, 85]
        },
        {
            "name": "Bob",
            "grades": [70, 75, 80]
        }
    ]
}

要提取所有学生的第一个成绩,正确的表达式是 $.students[*].grades[0] ,如果写成 $.students.grades[0] 则会出错。
- 过滤条件不生效 :检查过滤条件中的运算符和谓词是否正确,确保字符串字面量使用了正确的引号,例如:

{
    "students": [
        {
            "name": "Alice",
            "grade": "A"
        },
        {
            "name": "Bob",
            "grade": "B"
        }
    ]
}

要提取成绩为 A 的学生,正确的表达式是 $.students[?(@.grade == 'A')] ,如果引号使用不正确,就会导致过滤条件不生效。

通过掌握Gson和JsonPath的使用方法,我们可以更高效地处理JSON数据,实现Java对象与JSON字符串之间的转换以及从JSON文档中提取所需的值。在实际应用中,根据具体的需求选择合适的方法和工具,同时注意相关的注意事项和常见问题的解决方案,以确保程序的正确性和性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值