前回の記事「多くの調査の結果、ついにFastJsonを禁止することが決定されました!「」では、FastJsonの基本的な使用法と既存の不確実性について話しました。そこで、最終的にプロジェクトでの使用を中止し、SpringBootにデフォルトでバインドされている市場の主流のJSONライブラリであるJacksonを選択することにしました。
この記事では、Jacksonの基本的な使用法と、SpringBootとの組み合わせと実践について説明します。
ジャクソンとは
Jacksonは比較的主流のJavaベースのJSONライブラリであり、JsonとXMLおよびJavaBeanの間のシリアル化と逆シリアル化に使用できます。
はい、Jacksonは、jackson-dataformat-xmlコンポーネントに基づいてJavaBeanとXMLの間の変換も処理でき、JDKに付属するXML実装よりも効率的で安全です。さらに使用するのは、JSONとJavaBeanの間の関数を処理することです。
ジャクソンはどのくらい主流ですか?Mavenウェアハウスのみの統計から、Jacksonの使用量が1位になります。Spring Bootでサポートされている3つのJSONライブラリ(Gson、Jackson、JSON-B)の中で、Jacksonが推奨されるデフォルトライブラリです。
Jacksonには、依存関係が少ない、シンプルで使いやすい、大きなJsonの高速解析、メモリフットプリントが小さい、柔軟なAPI、便利な拡張とカスタマイズなどの特徴もあります。
JacksonクラスライブラリのGitHubアドレス:https://github.com/FasterXML/jackson。
ジャクソンのコンポーネント
Jacksonのコアモジュールは、jackson-core、jackson-annotations、およびjackson-databindの3つの部分(Jackson 2.x以降)で構成されています。
- jackson-core:コアパッケージは低レベルストリーミング(ストリーミング)APIを定義し、「ストリーミングモード」に基づいた分析を提供します。Jacksonの内部実装は、高性能ストリーミングモードAPIのJsonGeneratorとJsonParserを介してjsonを生成および解析することです。
- jackson-annotations、Annotationsパッケージは、標準のJacksonアノテーション関数を提供します。
- jackson-databind:データバインディング(およびオブジェクトのシリアル化)のサポートを実装するDatabindパッケージ。ストリーミングおよびアノテーションパッケージに依存しています。「オブジェクトバインディング」解決(ObjectMapper)および「ツリーモデル」解決API(JsonNode)に基づくAPIを提供します。「オブジェクトバインディング」解決に基づくAPIおよび「ツリーモデル」のAPI解決は、「ストリームモード」APIに基づく解決に依存します。
さまざまな環境での関連コンポーネントの依存関係の紹介を見てみましょう。
SpringBootでは、spring-boot-starter-webが間接的にJacksonコンポーネントを導入します。つまり、SpringBootフレームワークを使用している場合、プロジェクトにはすでにJacksonの依存関係があります。次の依存関係では、バージョンとスコープの項目が省略されています。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Webスターターはjsonスターターに依存しています:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
jsonスターターはついにジャクソンを紹介しました:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
上記のように、jackson-databindはストリーミングおよびアノテーションパッケージに依存しているため、jackson-databindの導入は、jackson-coreおよびjackson-annotationsの導入と同等です。
通常、これを単独で使用する場合、必要に応じてMavenを介してjackson-databind、jackson-core、およびjackson-annotationsをインポートできます。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
SpringBootプロジェクトの場合、基本的に依存関係を追加する必要はありません。
ジャクソンコアクラスObjectMapper
Jacksonは、データバインディング、JSONツリーモデル、ストリーミングAPIの3つのJSON処理方法を提供します。最初の2つの関数はObjectMapperに基づいて実装されますが、ストリーミングAPI関数は下位レベルのJsonGeneratorとJsonParserに基づいて実装する必要があります。
通常の状況では、次の関数を持つObjectMapperクラスを使用するだけで十分です。
- 文字列、ストリーム、またはファイルからJSONを解析し、解析されたJSONを表すJavaオブジェクト(逆シリアル化)を作成します。
- JavaオブジェクトをJSON文字列に構築します(シリアル化)。
- JSONをカスタムクラスのオブジェクトに解析するか、JSONツリーモデルのオブジェクトを解析します。
ObjectMapperは、JSONの実際の読み取り/書き込みを実装するためにJsonParserとJsonGeneratorに基づいています。これは、ObjectMapperの構築方法を見れば理解できます。
具体例
ジャクソンの一般的な使用法を1つずつ説明することはしません。一連の例を通して紹介します。各例はコメントで説明します。
一般的で簡単な使用
次の例は、主にJavaBeanとJson文字列間の変換を含む、私たちがよく使用する使用法のデモンストレーションです。
JacksonがjsonをJavaBeanプロパティに変換すると、デフォルトでは、Jsonフィールドの名前をJavaオブジェクトのgetterメソッドおよびsetterメソッドと照合してバインドします。
Jacksonは、getterメソッドとsetterメソッドの名前から「get」と「set」の部分を削除し、最初の文字を下げます。たとえば、Jsonの名前は、JavaBeanのgetName()およびsetName()と一致します。
ただし、すべての属性をシリアル化および逆シリアル化できるわけではありません。基本的に、次のルールに従います。
- パブリックに変更された属性は、シリアル化および逆シリアル化できます。
- このプロパティは、シリアル化および逆シリアル化できるパブリックゲッター/セッターメソッドを提供します。
- このプロパティには、パブリックセッターメソッドのみがあり、パブリックゲッターメソッドはありません。このプロパティは、逆シリアル化にのみ使用できます。
@Slf4j
public class JacksonTest {
/**
* JavaBean转JSON字符串
*/
@Test
public void testJavaBeanToJson() {
WeChat weChat = new WeChat();
weChat.setId("zhuan2quan");
weChat.setName("程序新视界");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
ObjectMapper mapper = new ObjectMapper();
try {
String result = mapper.writeValueAsString(weChat);
System.out.println(result);
} catch (JsonProcessingException e) {
log.error("转换异常", e);
}
}
/**
* JSON字符串转JavaBean
*/
@Test
public void testJsonToJavaBean() {
String json = "{\"id\":\"zhuan2quan\",\"name\":\"程序新视界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
ObjectMapper mapper = new ObjectMapper();
try {
WeChat weChat = mapper.readValue(json, WeChat.class);
System.out.println(weChat);
} catch (JsonProcessingException e) {
log.error("解析异常", e);
}
}
/**
* JSON字符串转Map集合
*/
@Test
public void testJsonToMap() {
String json = "{\"id\":\"zhuan2quan\",\"name\":\"程序新视界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
ObjectMapper mapper = new ObjectMapper();
try {
// 对泛型的反序列化,使用TypeReference可以明确的指定反序列化的类型。
Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
});
System.out.println(map);
} catch (JsonProcessingException e) {
log.error("解析异常", e);
}
}
/**
* JavaBean转文件
*/
@Test
public void testJavaBeanToFile() {
WeChat weChat = new WeChat();
weChat.setId("zhuan2quan");
weChat.setName("程序新视界");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
ObjectMapper mapper = new ObjectMapper();
try {
//写到文件
mapper.writeValue(new File("/json.txt"), weChat);
//从文件中读取
WeChat weChat1 = mapper.readValue(new File("/json.txt"), WeChat.class);
System.out.println(weChat1);
} catch (IOException e) {
log.error("转换异常", e);
}
}
/**
* JavaBean转字节流
*/
@Test
public void testJavaBeanToBytes() {
WeChat weChat = new WeChat();
weChat.setId("zhuan2quan");
weChat.setName("程序新视界");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
ObjectMapper mapper = new ObjectMapper();
try {
// 写为字节流
byte[] bytes = mapper.writeValueAsBytes(weChat);
// 从字节流读取
WeChat weChat1 = mapper.readValue(bytes, WeChat.class);
System.out.println(weChat1);
} catch (IOException e) {
log.error("转换异常", e);
}
}
}
上記のコードは、ロンボクアノテーションとユニットテストアノテーションを使用しており、必要に応じて置き換えることができます。
JSONツリーモデル
Json文字列が比較的大きい場合は、JSONツリーモデルを使用して、必要なフィールドコンテンツを柔軟に取得できます。Jacksonは、get、path、has、およびgetまたは判断するための他のメソッドを提供します。
2つの例を直接見てみましょう。
@Slf4j
public class JacksonNodeTest {
/**
* JavaBean转JSON字符串
*/
@Test
public void testJsonNode() {
// 构建JSON树
ObjectMapper mapper = new ObjectMapper();
ObjectNode root = mapper.createObjectNode();
root.put("id", "zhuan2quan");
root.put("name", "程序新视界");
ArrayNode interest = root.putArray("interest");
interest.add("Java");
interest.add("Spring Boot");
interest.add("JVM");
// JSON树转JSON字符串
String json = null;
try {
json = mapper.writeValueAsString(root);
} catch (JsonProcessingException e) {
log.error("Json Node转换异常", e);
}
System.out.println(json);
}
/**
* 解析JSON字符串为JSON树模型
*/
@Test
public void testJsonToJsonNode() {
String json = "{\"id\":\"zhuan2quan\",\"name\":\"程序新视界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
ObjectMapper mapper = new ObjectMapper();
try {
// 将JSON字符串转为JSON树
JsonNode jsonNode = mapper.readTree(json);
String name = jsonNode.path("name").asText();
System.out.println(name);
JsonNode interestNode = jsonNode.get("interest");
if (interestNode.isArray()){
for (JsonNode node : interestNode){
System.out.println(node.asText());
}
}
} catch (JsonProcessingException e) {
log.error("Json Node转换异常", e);
}
}
}
getメソッドはpath関数に似ています。違いは、読み取るキーがJson文字列に存在しない場合、getメソッドはnullになり、pathはMissingNodeインスタンスオブジェクトを返すことです。 linkメソッドの場合、例外がスローされないことが保証されます。
ストリーミングAPI
上記の2つの形式に加えて、主にJsonGeneratorとJsonParserの2つのAPIを介して、基盤となるストリーミングAPIに基づいて操作することもできますが、操作はより複雑なので、ここでは説明しません。
統一された構成をフォーマットする
ObjectMapperを使用する場合、シリアル化または逆シリアル化する必要のないフィールドがいくつかあり、フォーマットされた情報を指定する必要がある場合があります。この時点で、ObjectMapperを介して構成できます。
//反序列化时忽略json中存在但Java对象不存在的属性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//序列化时日期格式默认为yyyy-MM-dd'T'HH:mm:ss.SSSZ
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//序列化时自定义时间日期格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//序列化时忽略值为null的属性
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//序列化时忽略值为默认值的属性
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);
構成アイテムについては、新しいObjectMapper実装クラスJsonMapperがバージョン2.2で追加されました。これは、ObjectMapperと同じ機能を備えています。ただし、ビルダーメソッドが追加されました。JsonMapper.builder()。configure()メソッドを介して直接構成でき、最後にJsonMapperオブジェクトが取得されます。JsonMapperの他のメソッドは、基本的にObjectMapperから統合されています。
注釈の使用
上記の統合構成を使用して、グローバル形式のシリアル化と逆シリアル化を構成できますが、一部の個別のシナリオでは、特定のフィールドを構成する必要があり、注釈が必要です。たとえば、Json文字列のフィールドがJavaオブジェクトの属性と矛盾している場合、それらの直接的な関係を確立するためにアノテーションが必要です。
JavaBeanフィールドで使用される@JsonPropertyは、JSONマッピングのフィールドを指定します。デフォルトでは、マップされたJSONフィールドは注釈付きフィールドと同じ名前になります。マップされたJSONのフィールド名は、value属性を介して指定できます。
@JsonIgnoreは、フィールド、ゲッター/セッター、およびコンストラクターパラメーターで使用できます。指定されたフィールドは、シリアル化および逆シリアル化に関与しません。
@JsonIgnorePropertiesはクラスに作用し、@ JsonIgnoreProperties({"prop1"、 "prop2"})は、シリアル化時にpro1とpro2の2つのプロパティを無視します。@JsonIgnoreProperties(ignoreUnknown = true)は、逆シリアル化時にクラスに存在しないフィールドを無視します。
@JsonFormatはフィールドに作用し、通常はフォーマット操作に使用されます。
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;
JavaBeanの時間フィールドがJDK8の新しい日時(LocalDate / LocalTime / LocalDateTime)タイプを使用する場合は、jackson-datatype-jsr310依存関係を追加する必要があります。依存関係の部分について話すとき、これはSpringBootによってデフォルトで導入された依存関係にあります。
もちろん、@ JsonPropertyOrder、@ JsonRootName、@ JsonAnySetter、@ JsonAnyGetter、@ JsonNamingなど、他にもいくつかの注釈があります。これらを使用する場合は、対応するドキュメントと例を参照して確認できます。ここにリストされています。
カスタムパーサー
上記の注釈と統合構成でまだニーズを満たせない場合は、パーサーをカスタマイズできます。例は次のとおりです。
public class MyFastjsonDeserialize extends JsonDeserializer<Point> {
@Override
public Point deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
Iterator<JsonNode> iterator = node.get("coordinates").elements();
List<Double> list = new ArrayList<>();
while (iterator.hasNext()) {
list.add(iterator.next().asDouble());
}
return new Point(list.get(0), list.get(1));
}
}
定義が完了したら、マッパーに登録します。
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Point.class, new MyFastjsonDeserialize());
objectMapper.registerModule(module);
ジャクソンはXMLを処理します
Jacksonは、jackson-dataformat-xmlパッケージを介してXMLを処理する機能も提供できます。XMLを処理するときは、woodstox-coreパッケージを使用することをお勧めします。これはXML実装であり、JDKに付属するXML実装よりも効率的で安全です。
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
Java 9以降を使用する場合、java.lang.NoClassDefFoundError:javax / xml / bind / JAXBExceptionが発生する可能性があります。これは、Java 9がJDKのモジュール化を実装し、元々JDKにパッケージ化されていたJAXB実装を分離するためです。したがって、JAXBの実装を手動で追加する必要があります。
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
以下はコード例で、基本的にJSON APIと非常によく似ています。XmlMapperは実際にはObjectMapperのサブクラスです。
@Test
public void testXml(){
WeChat weChat = new WeChat();
weChat.setId("zhuan2quan");
weChat.setName("程序新视界");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
XmlMapper xmlMapper = new XmlMapper();
try {
String xml = xmlMapper.writeValueAsString(weChat);
System.out.println(xml);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
実行後、出力結果は次のとおりです。
<WeChat>
<id>zhuan2quan</id>
<name>程序新视界</name>
<interest>
<interest>Java</interest>
<interest>Spring Boot</interest>
<interest>JVM</interest>
</interest>
</WeChat>
SpringBootへの統合
最初に、Spring BootがデフォルトでJacksonの依存関係を導入し、追加の操作も行うことを確認しました。実際、すでにJacksonを使用してJson形式のデータをMVCのパラメーターにバインドしています。
Spring Bootのデフォルト構成がプロジェクトのニーズに適していない場合は、組み込み構成を使用して構成することもできます。application.yml構成を例にとると、次のプロパティを指定して、対応するオプションを構成できます。 :
#指定日期格式,比如yyyy-MM-dd HH:mm:ss,或者具体的格式化类的全限定名
spring.jackson.date-format
#是否开启Jackson的反序列化
spring.jackson.deserialization
#是否开启json的generators.
spring.jackson.generator
#指定Joda date/time的格式,比如yyyy-MM-ddHH:mm:ss). 如果没有配置的话,dateformat会作为backup
spring.jackson.joda-date-time-format
#指定json使用的Locale.
spring.jackson.locale
#是否开启Jackson通用的特性.
spring.jackson.mapper
#是否开启jackson的parser特性.
spring.jackson.parser
#指定PropertyNamingStrategy(CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)或者指定PropertyNamingStrategy子类的全限定类名.
spring.jackson.property-naming-strategy
#是否开启jackson的序列化.
spring.jackson.serialization
#指定序列化时属性的inclusion方式,具体查看JsonInclude.Include枚举.
spring.jackson.serialization-inclusion
#指定日期格式化时区,比如America/Los_Angeles或者GMT+10.
spring.jackson.time-zone
Spring Bootの自動構成は非常に便利ですが、自動構成されたBeanを置き換えるためにBeanを手動で構成する必要がある場合があります。次の形式で構成できます。
@Configuration
public class JacksonConfig {
@Bean
@Qualifier("json")
public ObjectMapper jsonMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper mapper = builder.createXmlMapper(false)
.build();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}
構成が完了したら、使用場所に直接挿入できます。
@Resource
private ObjectMapper jsonMapper;
上記の注入について、一部の友人は質問するかもしれません、スレッドセーフの問題はありますか?ObjectMapperがスレッドセーフであることを心配する必要はありません。
概要
この記事の説明の後、誰もがジャクソンをより包括的に理解する必要があります。個人的には、ジャクソンを学んだ後でも、それはまだかなり面白いと感じています。
元のリンク:「FastJsonを放棄してください!1つの記事で十分です、ジャクソンの機能はとても素晴らしいです(4D乾物)》