序
Java的核心库中没有内置JSON库。这不是一个问题,因为您可以从许多不同的第三方JSON库中进行选择。常用的库是Jackson和Gson,在这篇博客文章中,我们将看看JSON- p(用于JSON处理的Java API, JSR 374) 来自甲骨文的一个标准的JSON接口。
JSR 374指定了一个非常底层的处理库。该规范没有描述JSON和Java对象之间的数据绑定。为此,创建了第二个基于JSR 374的JSON绑定规范(JSON- b, JSR 367)。
与许多JSR一样,JSR 374只描述库必须实现的接口。对于下面的示例,我们使用Glassfish参考实现。
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.1.4</version>
</dependency>
JSR 374为JSON处理提供了两个model。Object Model API和Streaming API。对象模型API提供了一种使用JSON的简单方法,并将所有数据保存在内存中。流API是一种低级API,旨在高效地处理大量JSON数据。 API的主要入口点是类Json。该类提供静态方法来创建解析器、读取器、生成器和写入器。
Object Model API(对象模型API)
这个API为JSON对象和数组结构提供了不可变的对象模型。API使用构建器模式创建这些对象模型,API使用JsonReader从输入源读取JSON, JsonWriter将其写入输出源。
Write(写入)
要使用对象模型API创建JSON,您需要使用静态方法JSON.createobjectbuilder()创建JsonObjectBuilder的一个实例。 JsonObjectBuilder实例最初是空的,您需要调用add()来添加键/值对。该库为所有受支持的JSON数据类型提供add()变量。特殊的addNull()方法插入一个值为null的属性。 要创建一个数组,您需要一个JsonArrayBuilder实例,该实例由静态方法> Json.createArrayBuilder()返回。数组生成器提供add()方法来添加数组的各个条目。
package ch.rasc.jsonp;
import java.util.Map;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonWriter;
import javax.json.stream.JsonGenerator;
public class ObjectWrite {
public static void main(String[] args) {
JsonObject model = Json.createObjectBuilder()
.add("id", 1234)
.add("active", true)
.add("name", "cmm")
.addNull("password")
.add("roles", Json.createArrayBuilder().add("admin").add("user").add("operator"))
.add("phoneNumbers",
Json.createArrayBuilder()
.add(Json.createObjectBuilder().add("type", "mobile").add("number",
"111-111-1111"))
.add(Json.createObjectBuilder().add("type", "home").add("number",
"222-222-2222")))
.build();
JsonWriter defaultWriter = Json.createWriter(System.out);
defaultWriter.write(model);
//如果您需要一个可读的输出,您可以创建一个将PRETTY_PRINTING标志设置为on的写入器。
Map<String, Boolean> properties = Map.of(JsonGenerator.PRETTY_PRINTING, Boolean.TRUE);
JsonWriter customWriter = Json.createWriterFactory(properties)
.createWriter(System.out);
customWriter.write(model);
}
}
整个JSON将在内存中创建,可以用JsonWriter将其写入输出流。使用静态方法Json.createWriter创建writer。默认情况下,编写器以紧凑的形式在一行中打印JSON:
JsonWriter defaultWriter = Json.createWriter(System.out);
defaultWriter.write(model);
//{"id":1234,"active":true,"name":"cmm","password":null,"roles":["admin","user","operator"],"phoneNumbers":[{"type":"mobile","number":"111-111-1111"},{"type":"home","number":"222-222-2222"}]}
{
"id": 1234,
"active": true,
"name": "cmm",
"password": null,
"roles": [
"admin",
"user",
"operator"
],
"phoneNumbers": [
{
"type": "mobile",
"number": "111-111-1111"
},
{
"type": "home",
"number": "222-222-2222"
}
]
}
Read(读取)
要使用对象模型API读取JSON,可以使用静态方法JSON.createreader()创建一个JsonReader实例。该方法需要InputStream或Reader作为参数。根据JSON的形式,将带有readObject()或readArray()的JSON读入内存。这些方法返回一个JsonObject resp。JsonArray实例,允许访问数据。这些类提供get…()方法来检索键和数组条目的值。 JsonObject实现了Map接口,因此您可以使用从Map中知道的所有方法,例如JsonObject . keyset()来列出所有键。JsonArray是Collection、Iterable和List的子接口。 注意,如果您试图访问一个不存在的密钥,get…()方法将返回NullPointerException。为了防止这种情况发生,您可以使用containsKey()检查密钥是否存在。
package ch.rasc.jsonp;
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonValue;
import javax.json.JsonValue.ValueType;
public class ObjectRead {
public static void main(String[] args) {
String input = "{\"id\":1234,\"active\":true,\"name\":\"cmm\",\"password\":null,\"roles\":[\"admin\",\"user\",\"operator\"],\"phoneNumbers\":[{\"type\":\"mobile\",\"number\":\"111-111-1111\"},{\"type\":\"home\",\"number\":\"222-222-2222\"}]}";
try (JsonReader reader = Json.createReader(new StringReader(input))) {
JsonObject root = reader.readObject();
int id = root.getInt("id");
boolean active = root.getBoolean("active");
String name = root.getString("name");
System.out.println("id: " + id);
System.out.println("active: " + active);
System.out.println("name: " + name);
JsonValue password = root.get("password");
if (password == JsonValue.NULL) {
System.out.println("password is null");
}
JsonArray roles = root.getJsonArray("roles");
for (int i = 0; i < roles.size(); i++) {
String role = roles.getString(i);
System.out.println(" " + role);
}
if (!root.containsKey("firstName")) {
System.out.println("does not contain firstName");
}
// String firstName = root.getString("firstName");
// throws NullPointerException
JsonArray phoneNumbers = root.getJsonArray("phoneNumbers");
for (JsonValue phoneNumber : phoneNumbers) {
if (phoneNumber.getValueType() == ValueType.OBJECT) {
JsonObject obj = phoneNumber.asJsonObject();
System.out.println(obj.getString("type"));
System.out.println(obj.getString("number"));
}
}
}
}
}
Streaming API(流)
Streaming API由JsonParser和JsonGenerator接口组成。解析器用于以流方式读取JSON,生成器用于创建JSON。
Write
要使用StreamingAPI编写JSON,您需要一个JsonGenerator实例,它是静态方法JSON.createGenerator创建。该方法需要OutputStream或Writer作为参数。正如API的名称所暗示的那样,您将以流的方式创建JSON,并且生成器将持续地写入给定的输出流或写入器。例如writeStartObject()在被调用时写入字符{和writeStartArray()写入[。 生成器为每种JSON数据类型提供重载的write()方法。如果希望创建一个值为null的键,可以使用writeNull()方法。 确保使用writeEnd()关闭每个start对象(writeStartObject)和数组(writeStartArray)。如果writeStart*和writeEnd调用的数量不匹配,生成器将抛出异常。
package ch.rasc.jsonp;
import java.util.Map;
import javax.json.Json;
import javax.json.stream.JsonGenerator;
public class StreamWrite {
public static void main(String[] args) {
var properties = Map.of(JsonGenerator.PRETTY_PRINTING, Boolean.FALSE);
try (JsonGenerator jg = Json.createGeneratorFactory(properties)
.createGenerator(System.out)) {
jg.writeStartObject()
.write("id", 1234)
.write("active", true)
.write("name", "cmm")
.writeNull("password")
.writeStartArray("roles")
.write("admin")
.write("user")
.write("operator")
.writeEnd()
.writeStartArray("phoneNumbers")
.writeStartObject()
.write("type", "mobile")
.write("number", "111-111-1111")
.writeEnd()
.writeStartObject()
.write("type", "home")
.write("number", "222-222-2222")
.writeEnd()
.writeEnd()
.writeEnd();
}
}
}
Output:
{
"id": 1234,
"active": true,
"name": "cmm",
"password": null,
"roles": [
"admin",
"user",
"operator"
],
"phoneNumbers": [
{
"type": "mobile",
"number": "111-111-1111"
},
{
"type": "home",
"number": "222-222-2222"
}
]
}
Read
要使用Streaming API读取JSON,需要使用JSON.createparser静态方法创建一个JsonParser实例。这个类需要InputStream或Reader作为参数。有了JsonParser,应用程序就可以通过JSON从一个标记转移到下一个标记。与对象模型API相反,流API不会将整个JSON读入内存,而是应用程序使用next()请求下一个令牌,或者使用skip*()跳过JSON的一部分,解析器在需要时从Reader/InputStream加载数据。 对于每个令牌,解析器返回一个描述当前令牌的Event对象。next()返回当前事件。要读取该值,然后从JsonParser对象调用get…()方法。 JsonParser还提供了skipArray()和skipObject()方法,它们指示解析器跳过整个数组/对象,并将解析器推进到数组resp的末尾对象。
package ch.rasc.jsonp;
import java.io.StringReader;
import javax.json.Json;
import javax.json.stream.JsonParser;
import javax.json.stream.JsonParser.Event;
public class StreamRead {
public static void main(String[] args) {
String input = "{\"id\":1234,\"active\":true,\"name\":\"Duke\",\"password\":null,\"roles\":[\"admin\",\"user\",\"operator\"],\"phoneNumbers\":[{\"type\":\"mobile\",\"number\":\"111-111-1111\"},{\"type\":\"home\",\"number\":\"222-222-2222\"}]}";
try (JsonParser parser = Json.createParser(new StringReader(input))) {
while (parser.hasNext()) {
Event event = parser.next();
switch (event) {
case START_OBJECT:
System.out.println("START_OBJECT");
break;
case END_OBJECT:
System.out.println("END_OBJECT");
break;
case START_ARRAY:
System.out.println("START_ARRAY");
break;
case END_ARRAY:
System.out.println("END_ARRAY");
break;
case KEY_NAME:
System.out.print("KEY_NAME: ");
System.out.println(parser.getString());
break;
case VALUE_NUMBER:
System.out.print("VALUE_NUMBER: ");
System.out.println(parser.getLong());
break;
case VALUE_STRING:
System.out.print("VALUE_STRING: ");
System.out.println(parser.getString());
break;
case VALUE_FALSE:
System.out.println("VALUE_FALSE");
break;
case VALUE_TRUE:
System.out.println("VALUE_TRUE");
break;
case VALUE_NULL:
System.out.println("VALUE_NULL");
break;
default:
System.out.println("something went wrong: " + event);
break;
}
}
}
}
}
下面的另一个例子演示了如何使用流API从JSON中读取数据。该示例从usgs.gov加载一个GeoJSON文件,其中包含了过去30天发生的地震。该程序提取每次地震的震级和位置,并将其打印到控制台。
package ch.rasc.jsonp;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import javax.json.Json;
import javax.json.stream.JsonParser;
import javax.json.stream.JsonParser.Event;
public class StreamReadEarthquakes {
public static void main(String[] args) throws IOException, InterruptedException {
String url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_month.geojson";
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder().GET().uri(URI.create(url)).build();
HttpResponse<InputStream> response = client.send(request,
BodyHandlers.ofInputStream());
try (JsonParser parser = Json.createParser(response.body())) {
while (parser.hasNext()) {
Event event = parser.next();
if (event == Event.KEY_NAME) {
String key = parser.getString();
if (key.equals("mag")) {
parser.next();
System.out.println(parser.getBigDecimal());
}
else if (key.equals("place")) {
parser.next();
System.out.println(parser.getString());
System.out.println();
}
}
}
}
}
}
JSON Pointer(指针)
JSON指针是JavaScript对象表示法(JSON)指针标准的实现。 JSON指针是引用JSON对象中的元素的字符串。使用JSON指针,应用程序可以检索值,还可以修改JSON对象。 要创建指针,可以调用静态方法Json.createPointer()并将指针表达式作为参数传递,factory方法返回一个JsonPointer实例。
Read
下面的示例从usgs.gov读取地震GeoJSON文件。文件是这样的:
{
type: "FeatureCollection",
metadata: {
...
},
bbox: [
...
],
features: [
{
type: "Feature",
properties: {
mag: Decimal,
place: String,
time: Long Integer,
updated: Long Integer,
...
应用程序使用三个JSON指针访问完整的features数组,并读取数组第一个元素的大小和位置。注意,每个指针表达式都以/开头,/表示根对象。特殊的/
package ch.rasc.jsonp;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonPointer;
import javax.json.JsonReader;
public class PointerGet {
public static void main(String[] args) throws IOException, InterruptedException {
String url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_month.geojson";
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder().GET().uri(URI.create(url)).build();
HttpResponse<InputStream> response = client.send(request,
BodyHandlers.ofInputStream());
JsonPointer allPointer = Json.createPointer("/features");
JsonPointer magPointer = Json.createPointer("/features/0/properties/mag");
JsonPointer placePointer = Json.createPointer("/features/0/properties/place");
try (JsonReader reader = Json.createReader(response.body())) {
JsonObject root = reader.readObject();
JsonArray all = (JsonArray) allPointer.getValue(root);
System.out.println("number of earthquakes: " + all.size());
System.out.println(magPointer.getValue(root));
System.out.println(placePointer.getValue(root));
}
}
}
Modify
JSON指针不仅可以用于检索值,应用程序还可以使用JSON指针修改JSON元素。注意:JSON指针API只与对象模型API一起工作,因为它需要访问整个JSON元素。
Add
要插入一个新的键/值对,需要创建一个指向不存在键的指针,并调用add()方法。该方法需要一个JsonObject实例和一个value对象。 下面的示例将新密钥电子邮件插入JSON元素。
package ch.rasc.jsonp;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonPointer;
import javax.json.JsonValue;
public class PointerOperations {
public static void main(String[] args) {
JsonObject jsonObject = Json.createObjectBuilder().add("id", 1234).add("active", true)
.addNull("password").add("roles", Json.createArrayBuilder().add("admin")).build();
System.out.println(jsonObject);
// {"id":1234,"active":true,"password":null,"roles":["admin"]}
// add
JsonPointer emailPointer = Json.createPointer("/email");
JsonObject modifiedJsonObject = emailPointer.add(jsonObject,
Json.createValue("test@test.com"));
System.out.println(modifiedJsonObject);
// {"id":1234,"active":true,"password":null,"roles":["admin"],"email":"test@test.com"}
//要删除键,请创建指向现有键的指针并调用remove()。
// remove
JsonPointer passwordPointer = Json.createPointer("/password");
modifiedJsonObject = passwordPointer.remove(jsonObject);
System.out.println(modifiedJsonObject);
// {"id":1234,"active":true,"roles":["admin"]}
//若要替换值,请调用replace()并将新值作为第二个参数传递。
// replace
JsonPointer activePointer = Json.createPointer("/active");
modifiedJsonObject = activePointer.replace(jsonObject, JsonValue.FALSE);
System.out.println(modifiedJsonObject);
// {"id":1234,"active":false,"password":null,"roles":["admin"]}
//JSON指针API提供containsValue()方法来检查指针表达式是否指向JSON元素中存在的键
// containsValue
System.out.println(activePointer.containsValue(jsonObject));
JsonPointer lastNamePointer = Json.createPointer("/lastName");
if (!lastNamePointer.containsValue(jsonObject)) {
System.out.println("lastName does not exist");
}
//向数组添加值
//对于向数组添加元素,API支持特殊的-语法。负号表示一个元素应该被推到数组的末尾。
//下面的代码将dev添加到现有数组中。如果没有-语法,则需要重复所有现有的数组元素。如果不这样做,add()方法将覆盖现有数组值。
//使用特殊的/roles/-指针,您只需要指定新值并add()将新元素插入数组的末尾。
// Arrays
JsonPointer rolesPointer = Json.createPointer("/roles");
modifiedJsonObject = rolesPointer.add(jsonObject,
Json.createArrayBuilder().add("admin").add("dev").build());
System.out.println(modifiedJsonObject);
// {"id":1234,"active":true,"password":null,"roles":["admin","dev"]}
JsonPointer addRolesPointer = Json.createPointer("/roles/-");
modifiedJsonObject = addRolesPointer.add(jsonObject, Json.createValue("dev"));
System.out.println(modifiedJsonObject);
// {"id":1234,"active":true,"password":null,"roles":["admin","dev"]}
}
}
JSON Merge Patch(更新部分资源的部分数据)
JSON Merge Patch是更新部分资源的部分数据标准的实现。 merge patch是描述两个JSON元素之间差异的一种方法。JSON Merge patch本身就是一个JSON元素。它可以用于在两个或多个参与方之间保持两个JSON元素的同步。用于交换大型JSON对象。应用程序可以创建一个合并补丁并将其发送给其他方,而不是在每次发生更改时发送整个对象。然后,接收方将merge补丁应用到其版本的JSON元素中,并得到与源对象相同的最新JSON元素。 JSON Merge Patch只适用于对象模型API。要创建一个Merge Patch,您可以调用方法Json.createMergeDiff(),并将源对象和目标对象作为参数传递。 下面的示例从元素中删除活动键,并向roles数组添加一个新值。
package ch.rasc.jsonp;
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonMergePatch;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonValue;
public class Merge {
public static void main(String[] args) {
JsonObject source = Json.createObjectBuilder().add("id", 1234).add("active", true)
.add("roles", Json.createArrayBuilder().add("admin")).build();
System.out.println(source);
// {"id":1234,"active":true,"roles":["admin"]}
JsonObject target = Json.createObjectBuilder().add("id", 1234)
.add("roles", Json.createArrayBuilder().add("admin").add("dev")).build();
System.out.println(target);
// {"id":1234,"roles":["admin","dev"]}
JsonMergePatch mergePatch = Json.createMergeDiff(source, target);
System.out.println(mergePatch.toJsonValue());
// {"active":null,"roles":["admin","dev"]}
//还可以使用JSON . createmergepatch()从JSON字符串创建合并资源。
try (JsonReader reader = Json.createReader(
new StringReader("{\"active\":null,\"roles\":[\"admin\",\"dev\"]}"))) {
JsonMergePatch createdMergePatch = Json.createMergePatch(reader.readValue());
JsonValue modifiedSource = createdMergePatch.apply(source);
System.out.println(modifiedSource);
// {"id":1234,"roles":["admin","dev"]}
}
}
}
想要要应用合并资源数据,可以调用JsonMergePatch实例的apply()方法。该方法期望JSON对象并返回更改后的元素。
Shortcomings(缺点)
在上面的例子中,我们看到了merge patch对象{"active":null,"roles":["admin","dev"]},它描述了两个JSON对象之间的区别:
- Source: {"id":1234,"active":true,"roles":["admin"]}
- Target: {"id":1234,"roles":["admin","dev"]} 这揭示了JSON Merge Patch格式的一些缺点。 特殊的空值用于删除键/值对,因此无法将键设置为空值。 另一个缺点是合并参数不能描述数组操作。如果应用程序添加、删除或更改数组的元素,则合并参数必须包含整个数组。在上面的示例中,我们只向roles数组添加了dev,但是合并补丁必须在合并补丁中包含整个数组的内容。如果在包含数百或数千个元素的数组中添加或删除一个元素,情况会更糟。 另一个问题是应用合并参数不会导致错误。任何奇怪的参数都将被合并。
JSON Patch
JSON Patch是JavaScript对象符号(JSON)Patch 标准的实现。 与JSON Merge Patch一样,JSON Patch也是描述两个JSON对象之间变化的标准。JSON Patch通过在源对象中列出应用于目标JSON对象的操作数组来实现这一点。 JSON Patch本身就是这样一个JSON对象。
[
{
"op":"replace",
"path":"/id",
"value":4321
},
{
"op":"remove",
"path":"/active"
},
{
"op":"add",
"path":"/roles/1",
"value":"dev"
}
]
JSONPatch JSON始终是包含op和路径键以及可选值键的对象数组。op描述应该执行的操作。JSON Patch支持添加、复制、移动、删除、替换和测试操作。 path键描述JSON对象的哪个部分受到操作的影响,它是一个JSON指针表达式。 可选值键是一个将在操作中使用的普通JSON值(字符串、数字、对象、数组、布尔值、null)。只需要添加和替换操作。 要创建一个JSON Patch,应用程序调用静态JSON.creatediff()方法,并传递两个文档作为参数。
package ch.rasc.jsonp;
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonException;
import javax.json.JsonObject;
import javax.json.JsonPatch;
import javax.json.JsonReader;
import javax.json.JsonValue;
public class Patch {
public static void main(String[] args) {
JsonObject source = Json.createObjectBuilder().add("id", 1234).add("active", true)
.add("roles", Json.createArrayBuilder().add("admin")).build();
System.out.println(source);
// {"id":1234,"active":true,"roles":["admin"]}
JsonObject target = Json.createObjectBuilder().add("id", 4321)
.add("roles", Json.createArrayBuilder().add("admin").add("dev")).build();
System.out.println(target);
// {"id":4321,"roles":["admin","dev"]}
JsonPatch patch = Json.createDiff(source, target);
System.out.println(patch.toString());
// [{"op":"replace","path":"/id","value":4321},{"op":"remove","path":"/active"},{"op":"add","path":"/roles/1","value":"dev"}]
//还可以使用JSON字符串创建JSON Patch对象。createPatch方法。
try (JsonReader reader = Json.createReader(new StringReader(
"[{\"op\":\"replace\",\"path\":\"/id\",\"value\":4321},{\"op\":\"remove\",\"path\":\"/active\"},{\"op\":\"add\",\"path\":\"/roles/1\",\"value\":\"dev\"}]"))) {
JsonPatch createdPatch = Json.createPatch(reader.readArray());
JsonValue modifiedSource = createdPatch.apply(source);
System.out.println(modifiedSource);
// {"id":4321,"roles":["admin","dev"]}
}
//要应用更改,应用程序必须调用JsonPatch实例的apply()方法。该方法期望JSON文档作为参数并返回更改后的文档。
//创建JSON补丁的另一种方法是使用JsonPatchBuilder。使用构建器,您可以通过编程方式创建补丁。
JsonPatch builtPatch = Json.createPatchBuilder()
.replace("/id", 4321)
.add("/roles/1", "dev")
.remove("/active")
.build();
JsonValue modifiedSource = builtPatch.apply(source);
System.out.println(modifiedSource);
// {"id":4321,"roles":["admin","dev"]}
//特殊的JSON指针语法——可以用来将一个新元素推到数组的末尾。
builtPatch = Json.createPatchBuilder()
.add("/roles/-", "dev")
.copy("/uuid", "/id")
.move("/enabled", "/active")
.build();
modifiedSource = builtPatch.apply(source);
System.out.println(modifiedSource);
// {"id":1234,"roles":["admin","dev"],"uuid":1234,"enabled":true}
//test()操作可用于在应用补丁之前检查值。以下代码仅在id包含值4321时应用更改。
try {
builtPatch = Json.createPatchBuilder()
.test("/id", 4321)
.add("/roles/-", "dev")
.copy("/uuid", "/id")
.move("/enabled", "/active")
.build();
modifiedSource = builtPatch.apply(source);
System.out.println(modifiedSource);
}
catch (JsonException e) {
System.out.println(e.getMessage());
// The JSON Patch operation 'test' failed for path '/id' and value '4321'
}
}
}
本文示例的源代码存储在GitHub库中:https://github.com/ralscha/blog2019/tree/master/json-p
原文链接:https://golb.hplar.ch/2019/08/json-p.html
Q.E.D.
Comments | 0 条评论