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文档中提取所需的值。在实际应用中,根据具体的需求选择合适的方法和工具,同时注意相关的注意事项和常见问题的解决方案,以确保程序的正确性和性能。
超级会员免费看
3869

被折叠的 条评论
为什么被折叠?



