Jackson:JSONとJavaの変換にとどまらない多彩な機能

Jackson は、完成度の高い多機能なオープンソースプロジェクトで、Indeed でも利用やサポート、コントリビューションを行っています。2回に渡るこの連載では、Jacksonの作者であり主任メンテナーを務める私が、Jacksonの主力機能や拡張機能、課題についてご紹介します。

Photo of Cape Flattery by Tatu Saloranta, Jackson's creator

  フラッタリー岬(撮影:Tatu Saloranta)

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オブジェクトとして使用します。

以下にいくつか例を挙げます。

これらのフレームワーク、ライブラリ、クライアントを使用しているユーザーは、自分が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 です。現在サポートされるデータフォーマットには、以下のものがあります。

データフォーマットモジュールの使い方は、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からご覧ください。以下にその一部をご紹介します。

データ型モジュールの実装のほかに、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をご覧ください。