Jackson は、完成度の高い多機能なオープンソースプロジェクトで、Indeed でも利用やサポート、コントリビューションを行っています。2回に渡るこの連載では、Jacksonの作者であり主任メンテナーを務める私が、Jacksonの主力機能や拡張機能、課題についてご紹介します。
Jacksonの主力機能
JSONを読み取ったり返したりするWebサービスをJavaで開発する場合には、JavaオブジェクトとJSONを相互に変換するためのライブラリが必要です。この機能は、Javaの標準ライブラリ(JDK)にはありませんが、JacksonライブラリのJavaオブジェクトをJSONとして書き込み、JSONをJavaオブジェクトとして読み取る主力機能を使えます。記事の後半で、Jacksonのその他の機能についても紹介します。
Java JSONライブラリとしてのJacksonには、以下の特長があります。
- 多機能
- 設定を柔軟に変更でき、パフォーマンスが高い
- 完成度と信頼性が高い(このプロジェクトの開始は13年前の2007年)
- 幅広く利用され、信頼されている
JacksonはMaven Centralから毎月2,500万回ダウンロードされており、16,000件のプロジェクトでjackson-databind
が使用されています。Core Infrastructure Initiativeの調査結果によると、JacksonはGuavaの次に広く利用されているJavaライブラリです。
Jacksonは直接使うこともできますが、今どきのユーザーは、Jackson機能が使われているライブラリやフレームワークを使うことの方が多いでしょう。こうしたライブラリやフレームワークでは、デフォルトでJSONのやり取りにJacksonを使用して、JSONのリクエストやレスポンスを処理したり、JSON設定ファイルをJavaオブジェクトとして使用します。
以下にいくつか例を挙げます。
- サーバーサイドフレームワーク:Spring/Spring Boot, DropWizard, RESTeasy, Restlet
- HTTPクライアントライブラリ:OkHttp, Retrofit
- アプリケーションのクライアント:aws-java-sdk, elastic-search-client
- データ処理フレームワーク:Apache Spark, Apache Flink, Apache Hadoop
これらのフレームワーク、ライブラリ、クライアントを使用しているユーザーは、自分がJacksonを使っていることに気づいていない場合もあります。この事実が分かるのはたいてい、使用上の問題のトラブルシューティングをするときや、デフォルトの処理を変更するときです。
Jacksonの使用例
以下の例は、Spring Boot フレームワークで要求/応答モデルのアノテーションを記述する方法を示しています。
// Model of updated and accessed content public class User { private Long id; private String name; @JsonProperty(“dob”) // customize name used in JSON private LocalDate dateOfBirth; private LocalDateTime lastLogin; // plus public Getters, Setters } // Service endpoint definition @RestController @RequestMapping("/users") public class UserController { // ... @GetMapping("/{id}") public User findUserById(@PathVariable Long id) { return userStorage.findUserById(id); } @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.CREATED) public Long createUser(@RequestBody User user) { return userStorage.createUser(user); } }
この例では、Spring BootフレームワークはJSONをUserクラスのインスタンスとして読み取ります。POSTメソッドでは、Userインスタンスをストレージハンドラーに引き渡します。さらに、この例は指定されたIDのUserインスタンスをストレージハンドラーによって取り出し、そのインスタンスのシリアライズされたJSONを書き込みます。詳しい説明は、「Jackson JSON Request and Response Mapping in Spring Boot」を参照してください。
JSONの読み書きは、以下のようにJackson APIによって直接行うこともでき、この場合はフレームワークもライブラリも不要です。
final ObjectMapper mapper = new ObjectMapper(); // Read JSON as a Java object: User user; try (final InputStream in = requestContext.getInputStream()) { user = mapper.readValue(in, User.class); } // Write a Java object as JSON try (final OutputStream out = requestContext.getOutputStream()) { mapper.writeValue(out, user); }
Jacksonの機能はJSONとJavaの変換だけではない
Jacksonの主力機能は、当初はJSONをJava対応にすることでしたが、モジュールによって急速に拡張されました。モジュールは、Jacksonのコアにプラグ可能な拡張コンポーネントで、コアがデフォルトでは取り扱わない機能に対応しています。Jacksonにはその他のJVM言語用の拡張もあります。
特に便利なJacksonモジュールと拡張のタイプをいくつか挙げます。
- データフォーマットモジュール
- データ型モジュール
- Kotlin、Scala、Clojure用モジュール
データフォーマットモジュール
データフォーマットモジュールを使えば、JSON以外のフォーマットでエンコードされたコンテンツを読み書きできます。JSONの低レイヤーのエンコードに関する詳細はYAML、CSV、Protobufとは異なりますが、より高レイヤーのデータバインド機能は、似ているか同一です。高レイヤーのデータバインドは、Javaオブジェクトの構造を処理して、トークンストリームとして表現します(または、それと同じような抽象化を行います)。
Jacksonのコアのコードはフォーマットに依存しないものが大部分で、JSONフォーマットに固有の部分はごくわずかです。このため、このようなデータフォーマットモジュールによってJacksonを簡単に拡張して、ほかのデータフォーマットのコンテンツを読み書きできるようになります。これらのモジュールは、低レイヤーのストリーミングをJackson APIから実装しますが、Javaオブジェクトとコンテンツを互いに変換する際には、共通のデータバインド機能を使います。
これらのモジュールの実装を見てみると、ストリーミングパーサーとジェネレーター用のfactoryがあるのが分かります。これらのパーサーとジェネレーターは、特定のファクトリを使用してフォーマット特有の詳細を処理するObjectMapper
を構成します。一方、ユーザーがやり取りする対象はフォーマットにほとんど依存しない、マッパーによって抽象化されたものだけです。
Jacksonに最初に追加されたデータフォーマットモジュールは、XML用の jackson-dataformat-xml です。現在サポートされるデータフォーマットには、以下のものがあります。
- テキストフォーマット
- YAML, CSV, プロパティファイル (jackson-dataformats-text)
- バイナリフォーマット
- バイナリJSON (CBOR, Smile) (jackson-dataformats-binary)
- Avro, Protobuf, Amazon Ion (jackson-dataformats-binary)
- MessagePack (jackson-dataformat-msgpack)
- BSON (bson4jackson, MongoJack など)
データフォーマットモジュールの使い方は、Jackson API JSONの使い方と似ていて、JsonMapper
(または汎用のObjectMapper
)を、XmlMapper
などのフォーマット別のマッパーに置き換えるだけです。フォーマットに固有の機能が存在し、これらを有効にするか無効にするかはユーザーが設定できます。また、データフォーマットによっては、コンテンツをJSONライクなトークン表現にマッピングするために追加のスキーマ情報が必要なものがありますが(Avro、CSV、Protobufなど)、APIの使い方はすべてのフォーマットで同様です。
例
先ほど紹介した、単純にJSONの読み書きを行う例を比較対象として、その他のデータフォーマットの読み書きを行う例を示します。
// XML usage is almost identical except it uses a different mapper object ObjectMapper xmlMapper = new XmlMapper(); String doc = xmlMapper.writeValueAsString(user); User user = xmlMapper.readValue(doc, User.class); // YAML usage is almost identical except it uses a different mapper object ObjectMapper yamlMapper = new YAMLMapper(); byte[] asBytes = yamlMapper.writeValueAsString(user); User user2 = yamlMapper.readValue(asBytes, User.class); // CSV requires Schema for most operations (to convert between property // names and column positions) // You can compose the schema manually or generate it from POJO. CsvMapper csvMapper = new CsvMapper(); CsvSchema userSchema = csvMapper.schemaFor(User.class); String csvDoc = csvMapper.writer(schema) .writeValueAsString(user); User user3 = csvMapper.readerFor(User.class) .with(schema) .readValue(csvDoc);
データ型モジュール
Jacksonのコアでは、Plain Old Java Object(POJO)と、ほとんどの標準JDKデータ型(文字列、数値、ブール、配列、コレクション、マップ、日付、カレンダー、URL、UUID)の読み書きが可能です。一方、Javaプロジェクトの多くは、Guava, Hibernate, Jodaなどのサードパーティライブラリによって定義される値型も使用します。これらの値型のインスタンスを単純なPOJOとして扱うと、特にコレクション型の場合にはうまく動作しません。明示的にサポートされていない限り、シリアライザやデシリアライザなどのハンドラーをユーザーが独自に実装してJacksonの機能を拡張する必要があります。Jacksonのコアにこのような明示的サポートを追加することは、最も一般的なJavaライブラリに対象を絞ったとしても大変な仕事で、難しい依存関係につながる可能性もあります。
この課題を解決するために、Jacksonにはデータ型モジュールという概念が付け加えられました。これらは、コアのJacksonとは別にビルドされてパッケージ化される拡張で、構築時にObjectMapper
インスタンスに追加されます。データ型モジュールのリリースは、Jacksonチームと外部コントリビューター(サードパーティライブラリの作成者など)のどちらもが行います。これらのモジュールが追加される理由としてよくあるのは、作成者が特定のユースケースを解決する必要があって、その努力の成果をほかの技術者たちと共有するためです。
これらのモジュールはプラグ可能なので、異なるフォーマットのデータ型モジュールを使用でき、異なる値型を混合することも可能です。たとえば、Jodaで定義されたPeriodの値を含む、Hibernateに基づくGuava ImmutableListをJSON、CSV、またはXMLのフォーマットで読み書きできます。
データ型モジュールのリストはJackson Portalからご覧ください。以下にその一部をご紹介します。
- Guava (jackson-datatypes-collections の一部)
- Hibernate (jackson-datatype-hibernate)
- Joda (jackson-datatype-joda)
- Java 8 日付と時刻 (jackson-datatype-jsr310)
データ型モジュールの実装のほかに、Jacksonデータ型モジュールの使用を直接サポートするフレームワークも多くあります。特に、以下のような各種の「イミュータブル」なライブラリがこのサポートを提供します。
Kotlin、Scala、Clojure用モジュール
Jacksonを使う場合に、ほかのJVM言語でPOJOとJDK型しか使えなくなることはありません。Jacksonには、ほかの多数のJVM言語でカスタム型を扱うための拡張が用意されています。
以下のJacksonモジュールがKotlinとScalaをサポートしています。
- Jackson-module-kotlinは、Kotlin Dataクラス、Range、Pair、Triple、および一般的なKotlinスタイルの使用をサポートします。
- Jackson-module-scalaは、Scala Caseクラス、Option、Either、Seq、およびScalaコレクション型をサポートします。
また、Clojureについては、同様なサポートを実装するためにJacksonを内部で使用するライブラリが、以下のようにいくつか存在します。
これによって相互運用がさらに簡素化され、Javaの機能をKotlin、Scala、Clojureから使用したり、逆にこれらをJavaから使用したりすることがもっと簡単になります。
次回予告
次回の記事では、Jacksonプロジェクトが今直面している課題と、その解決に皆さんがコントリビュートする方法についてお話しする予定です。
筆者紹介
Tatu Salorantaは、Indeed のスタッフソフトウェアエンジニアで、次世代の継続的デプロイメントシステムを統合するチームを率いています。Indeed の社外では、オープンソース分野での活動で広く知られる存在であり、@cowtowncoderのハンドルで、Jackson、Woodstox、lzf圧縮コーデック、Java ClassMateなど、多くの人気のあるオープンソースJavaライブラリを生み出しています。詳しい一覧についてはFasterXML Orgをご覧ください。