jackson-databind(java)のObjectMapperでバイナリを扱う際のエンコーディング指定方法

「jackson-databind」とは、JSONとjavaのオブジェクト間の相互変換等のために利用されるjavaのライブラリです。
「ObjectMapper」(ObjectMapper.java)とは「jackson-databind」において上記の機能を提供するクラスです。
(以降、簡単に「jackson」や「ObjectMapper」と表記します)

jacksonやObjectMapperの基本的な使い方に関しては、他の方の記事やjackson-databindのGitHubのドキュメントを参照ください(*1)。

当記事では、

- バイナリデータが与えられる(例：Httpリクエストのリクエストボディ、外部ファイルの入力)
- バイナリデータを出力する（例：Httpレスポンスのレスポンスボディ、外部ファイル書き込み)

場合の、ObjectMapperに対するエンコーディングの指定方法を紹介します。

また、ObjectMapperの動作の背景にあるJSONのエンコーディングの考え方についても言及します。

「エンコーディングの指定方法」は、ざっくり述べると「UTF-8(+α)以外の場合、Reader/Writerを渡すメソッドを使用する」という単純明快なものです。

# 動作確認した環境やVersion
- jackson-databind 2.9.8 (2019/5/3時点最新版)
- jdk11

# デモ用ソースコード
重要なポイントは以下の４点です。以降で(a)～(d)をそれぞれ詳しく見ていきます。
ソースコードは[GitHub](https://github.com/st-user/BinaryToJSONSample/blob/master/src/test/java/com/example/BinaryToJSONSampleTest.java)にもアップロードしています。

- (a). **UTF-8, UTF-16, UTF-32**を「read」する場合は、エンコーディングの指定は不要
（byte[]やInputStreamを引数に渡すタイプのメソッドを使用可能）
- (b). **UTF-8, UTF-16, UTF-32以外**を「read」する場合は、エンコーディングを明示したReaderを生成し、Readerを引数に渡すタイプのメソッドを使用する
- (c). **UTF-8**で「write」する場合は、エンコーディングの指定は不要（byte[]やInputStreamを引数に渡すタイプのメソッドを使用可能）
- (d). **UTF-8以外**で「write」する場合は、エンコーディングを明示したWriterを生成し、Writerを引数に渡すタイプのメソッドを使用する


##### この記事で使用する文言の補足

- 「read」: 「JSONのバイナリデータ to javaオブジェクト」の変換
- 「write」: 「javaオブジェクト to JSONのバイナリデータ」の変換

##### 調査対象としたObjectMapperのメソッド

- 「read」のメソッド
    - 「readValue」の内、第一引数がStringでないもの
    - 「readTree」の内、第一引数がStringでないもの
- 「write」のメソッド
    - 「wrteValue」
    - 「writeTree」
    - 「writeValueAsBytes」

「DataOutput」「DataInput」を引数に取るメソッドは他と比べて若干特殊なため「Appendix」で紹介します。

上記のメソッドは様々な引数を取れるようにオーバーロードされています。
対象のメソッドが多いため、対象のすべてのメソッドについてサンプルソースを掲載しているわけではありません。

## 準備

以降のソースコードで以下の変数やクラスを共通で使用します。

なお、以降のソースコードではリソースのクローズ処理が必要な個所でも一律割愛しています。

``` java

	ObjectMapper mapper = new ObjectMapper();

	String json = "{\"message\":\"あいうえお\"}"; // 「read」するJSON
	String readExpected = new Bean("あいうえお").toString();// 「read」の期待値

	Bean bean = new Bean("かきくけこ");// 「write」するjavaのオブジェクト
	String writeExpected = "{\"message\":\"かきくけこ\"}";// 「write」の期待値
	
	static class Bean {
		private String message;
		//setter ,getter, コンストラクタ, は割愛
		public String toString() {
			return "Bean [message=" + message + "]";
		}
	}
```

## 詳細
#### (a). **UTF-8, UTF-16, UTF-32**を「read」する場合は、エンコーディングの指定は不要

UTF-8, UTF-16, UTF-32を「read」する場合は、エンコーディングが自動で判定されるため、
byte[]やInputStreamを引数に渡すタイプのメソッドを使用可能です。

``` java

	/*
	 * (a) UTF-8, UTF-16, UTF-32を「read」する場合は、エンコーディングの指定は不要
	 * 
	 * String#getBytesを使用し、任意のエンコーディングのバイトデータが与えられた状況をシミュレートしています
	 *  確認に使用しているメソッド :
	 * ObjectMapper#readValue(byte[], Class<T>)<T> : T
	 */
	@Test
	public void readValueFromUTF8or16or32ByteArrayShouldDetectEncodingSuccessfully() throws Exception {

		Bean beanFromUTF32BE = mapper.readValue(json.getBytes("UTF-32BE"), Bean.class);
		Bean beanFromUTF16BE = mapper.readValue(json.getBytes("UTF-16BE"), Bean.class);
		Bean beanFromUTF32LE = mapper.readValue(json.getBytes("UTF-32LE"), Bean.class);
		Bean beanFromUTF16LE = mapper.readValue(json.getBytes("UTF-16LE"), Bean.class);
		Bean beanFromUTF8 = mapper.readValue(json.getBytes("UTF-8"), Bean.class);

		assertThat(beanFromUTF32BE.toString(), equalTo(readExpected));
		assertThat(beanFromUTF16BE.toString(), equalTo(readExpected));
		assertThat(beanFromUTF32LE.toString(), equalTo(readExpected));
		assertThat(beanFromUTF16LE.toString(), equalTo(readExpected));
		assertThat(beanFromUTF8.toString(), equalTo(readExpected));

		// !! 以下を実行するとExceptionが発生します。
		// mapper.readValue(json.getBytes("Shift_JIS"), Bean.class);
		// -> 「com.fasterxml.jackson.databind.JsonMappingException: Invalid UTF-8 start byte 0x82」

	}

	/*
	 * (a) 上記の「 ObjectMapper#readValue(byte[], Class<T>)<T> : T 」以外の
	 * 「(a)」パターンとして利用できるメソッド
	 */
	@Test
	public void readMethodsFromAnyUTF8BinaryDataShouldDetectEncodingSuccessfully() throws Exception {

		File file = new File("sample_UTF-8.json");
		// 第一引数 : URL
		Bean beanFromURL = mapper.readValue(file.toURI().toURL(), Bean.class);
		assertThat(beanFromURL.toString(), equalTo(readExpected));

		// 第一引数 : File
		Bean beanFromFile = mapper.readValue(file, Bean.class);
		assertThat(beanFromFile.toString(), equalTo(readExpected));

		// 第一引数 : InputStream
		InputStream inputStream = new FileInputStream(file);
		Bean beanFromInputStream = mapper.readValue(inputStream, Bean.class);
		assertThat(beanFromInputStream.toString(), equalTo(readExpected));

		// 第一引数: JsonParser
		// 「createParser」に渡せる引数の種類は、「readValue」の第一引数と似ています
		// 以下は、byte[]を渡す例です。
		// 他にも、File, URL, InputStream, Readerを渡すメソッド等があります
		JsonParser jsonParser = mapper.getFactory().createParser(json.getBytes("UTF-8"));
		Bean beanFromJsonParser = mapper.readValue(jsonParser, Bean.class);
		assertThat(beanFromJsonParser.toString(), equalTo(readExpected));

		// 「readTree」の場合も、「readValue」と同じような引数を渡せます。
		// 以下は、byte[]を渡す例です。
		JsonNode jsonNode = mapper.readTree(json.getBytes("UTF-8"));
		assertThat(jsonNode.get("message").asText(), equalTo("あいうえお"));
	}

```

##### 背景にある（と想定される）JSONの仕様について

最新のJSONの仕様([RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt))では、クローズドなエコシステム以外では「UTF-8」の使用が必須とされています。
一方、現時点のjacksonは[RFC 4627](https://www.ietf.org/rfc/rfc4627.txt)を念頭に実装されているようです(*2)。

[RFC 4627](https://www.ietf.org/rfc/rfc4627.txt)の「3.  Encoding」よると、JSONテキストの先頭2文字は常にASCII文字であるため、JSONテキストの先頭の4バイト(*3)における「00」のパターンにより、「UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE)」が判断できます。
デモ用ソースコードで使用しているJSONテキストにて確認してみます。

JSONテキスト：{"message": "あいうえお"}（先頭2文字は「{"」）

|encoding|先頭4バイト|
|:-------|:-------|
|UTF-32BE|00 00 00 7B|
|UTF-16BE|00 7B 00 22|
|UTF-32LE|7B 00 00 00|
|UTF-16LE|7B 00 22 00|
|UTF-8|7B 22 6D 65|

先頭2バイトの両方が「00」以外であるのはUTF-8のみ、先頭2バイト両方が「00」なのはUTF-32BEのみ・・・・・といった要領でencodingが判定できることがわかります。

動作確認の結果、および以上の理解に基づくならば、UTF-8, UTF-16, UTF-32でエンコードされたバイトデータを「read」する場合には、エンコーディングの指定は不要といえると思われます。

#### (b). **UTF-8, UTF-16, UTF-32以外**を「read」する場合は、エンコーディングを明示したReaderを生成し、Readerを引数に渡すタイプのメソッドを使用する

jacksonのjavadoc(*4)にも記載されている通り、UTF-8, UTF-16, UTF-32以外のエンコーディングを使用する場合には、エンコーディングを明示したReaderを生成し、Readerを引数に渡すタイプのメソッドを使用します。

「(a)」に記載したように、jacksonが[RFC 4627](https://www.ietf.org/rfc/rfc4627.txt)を念頭に置いているならば、UTF-8, UTF-16, UTF-32以外のエンコーディングを自動判定できない（しない）のは自然な動作です(*5)。


``` java


	/*
	 * (b) UTF-8, UTF-16, UTF-32以外を「read」する場合は、
	 * エンコーディングを明示したReaderを生成し、Readerを引数に渡すタイプのメソッドを使用する
	 * 
	 * String#getBytesを使用し、任意のエンコーディングのバイトデータが与えられた状況をシミュレートしています 
	 * 確認に使用しているメソッド :
	 * ObjectMapper#readValue(Reader, Class<T>)<T> : T
	 */
	@Test
	public void readValueFromReaderShouldReadBytesOfSpecifiedEncoding() throws Exception {

		// 現実のアプリケーションの例：
		// 「InputStream」を HttpServletRequest#getInputStreamから取得する
		InputStream inputStream = new ByteArrayInputStream(json.getBytes("Shift_JIS"));
		Reader reader = new InputStreamReader(inputStream, "Shift_JIS");
		Bean beanFromReader = mapper.readValue(reader, Bean.class);
		assertThat(beanFromReader.toString(), equalTo(readExpected));
	}

	/*
	 * (b) 上記の「 ObjectMapper#readValue(Reader, Class<T>)<T> : T 」以外の
	 * 「(b)」パターンとして利用できるメソッド
	 */
	@Test
	public void readMethodsFromReaderShouldReadBytesOfSpecifiedEncoding() throws Exception {

		// 第一引数: JsonParser
		// 「createParser」にReaderを渡す
		Reader reader = new InputStreamReader(new ByteArrayInputStream(json.getBytes("Shift_JIS")), "Shift_JIS");
		JsonParser jsonParser = mapper.getFactory().createParser(reader);
		Bean beanFromJsonParser = mapper.readValue(jsonParser, Bean.class);
		assertThat(beanFromJsonParser.toString(), equalTo(readExpected));

		// 「readTree」でもReaderを渡す
		Reader readerToTree = new InputStreamReader(new ByteArrayInputStream(json.getBytes("Shift_JIS")), "Shift_JIS");
		JsonNode jsonNode = mapper.readTree(readerToTree);
		assertThat(jsonNode.get("message").asText(), equalTo("あいうえお"));
	}
```



#### (c). **UTF-8**で「write」する場合は、エンコーディングの指定は不要

エンコーディングの指定がないタイプの「write」するメソッドでは、エンコーディングはUTF-8となっています(*6)。
そのため、書き出すバイトデータのエンコーディングがUTF-8の場合、エンコーディングの明示は不要です。

``` java

	/*
	 * (c) UTF-8で「write」する場合は、エンコーディングの指定は不要
	 * 
	 * ByteArrayOutputStreamを使用し、OutputStreamにJSONを書き出す状況をシミュレートしています 
	 * 確認に使用しているメソッド
	 * : ObjectMapper#writeValue(OutputStream, Object) : void
	 */
	@Test
	public void writeValueNotSpecifyingEncodingShouldWriteUTF8Bytes() throws Exception {

		// 現実のアプリケーションの例：
		// 「OutputStream」を HttpServletResponse#getOutputStreamから取得する
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		mapper.writeValue(outputStream, bean);
		assertThat(outputStream.toByteArray(), equalTo(writeExpected.getBytes("UTF-8")));
	}

	/*
	 * (c) 上記の「 ObjectMapper#writeValue(OutputStream, Object) : void 」以外の
	 * 「(c)」パターンとして利用できるメソッド
	 */
	@Test
	public void writeMethodsNotSpecifyingEncodingShouldWriteUTF8Bytes() throws Exception {

		// 第一引数 : File
		File outFile = new File("out_sample.json");
		mapper.writeValue(outFile, bean);
		byte[] fileData = Files.readAllBytes(outFile.toPath());
		assertThat(fileData, equalTo(writeExpected.getBytes("UTF-8")));

		// 第一引数: JsonGenerator
		// 「createGenerator」に渡せる引数の種類は、「writeValue」の第一引数と似ています。
		// 以下はOutputStreamを渡す例です。
		// 他にもFileを渡すメソッド等があります。
		ByteArrayOutputStream jsonGeneratorOutputBytes = new ByteArrayOutputStream();
		JsonGenerator jsonGenerator = mapper.getFactory().createGenerator(jsonGeneratorOutputBytes);
		mapper.writeValue(jsonGenerator, bean);
		assertThat(jsonGeneratorOutputBytes.toByteArray(), equalTo(writeExpected.getBytes("UTF-8")));

		// 「writeValueAsBytes」
		assertThat(mapper.writeValueAsBytes(bean), equalTo(writeExpected.getBytes("UTF-8")));

		// 「writeTree」の第一引数は、「JsonGenerator」のため、
		// ことエンコーディングに関しては「writeValue」において「JsonGenerator」を使用する時と同じ要領で使用します
		ByteArrayOutputStream jsonGeneratorOutputBytesForTree = new ByteArrayOutputStream();
		JsonGenerator jsonGeneratorForTree = mapper.getFactory().createGenerator(jsonGeneratorOutputBytesForTree);
		JsonNode tree = mapper.readTree(writeExpected);
		mapper.writeTree(jsonGeneratorForTree, tree);
		assertThat(jsonGeneratorOutputBytesForTree.toByteArray(), equalTo(writeExpected.getBytes("UTF-8")));
	}

	/*
	 * (c)補足 JsonGeneratorを使用する場合に関する補足
	 * 「createGenerator」の引数に「JsonEncoding」を渡せる場合は、UTF-16, UTF-32も使用できます
	 */
	@Test
	public void writeValueByJsonGeneratorCanAlsoSpecifyUTF16orUTF32Encoding() throws Exception {

		ByteArrayOutputStream jsonGeneratorOutputBytes = new ByteArrayOutputStream();
		JsonGenerator jsonGenerator = mapper.getFactory().createGenerator(jsonGeneratorOutputBytes,
				JsonEncoding.UTF16_BE);
		mapper.writeValue(jsonGenerator, bean);

		assertThat(jsonGeneratorOutputBytes.toByteArray(), equalTo(writeExpected.getBytes("UTF-16BE")));
	}	

```



#### (d). **UTF-8以外**で「write」する場合は、エンコーディングを明示したWriterを生成し、Writerを引数に渡すタイプのメソッドを使用する

エンコーディングを明示したい場合は、jacksonのjavadoc(*5)にも記載されている通り、エンコーディングを明示したWriterを生成し、Writerを引数に渡すタイプのメソッドを使用します。

``` java
	/*
	 * (d) UTF-8以外で「write」する場合は、 エンコーディングを明示したWriterを生成し、
	 * Writerを引数に渡すタイプのメソッドを使用する
	 * 
	 * ByteArrayOutputStreamを使用し、OutputStreamにJSONを書き出す状況をシミュレートしています 
	 * 確認に使用しているメソッド
	 * : ObjectMapper#writeValue(Writer, Object) : void
	 */
	@Test
	public void writeValueFromWriterShouldWriteBytesOfSpecifiedEncoding() throws Exception {

		// 現実のアプリケーションの例：
		// 「OutputStream」を HttpServletResponse#getOutputStreamから取得する
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		OutputStreamWriter writer = new OutputStreamWriter(outputStream, "Shift_JIS");
		mapper.writeValue(writer, bean);

		assertThat(outputStream.toByteArray(), equalTo(writeExpected.getBytes("Shift_JIS")));
	}

	/*
	 * (d) 上記の「 ObjectMapper#writeValue(Writer, Object) : void 」以外の
	 * 「(d)」パターンとして利用できるメソッド
	 */
	@Test
	public void writeMethodsFromWriterShouldWriteBytesOfSpecifiedEncoding() throws Exception {

		// 第一引数: JsonGenerator
		// 「createGenerator」にWriterを渡す
		ByteArrayOutputStream jsonGeneratorOutputBytes = new ByteArrayOutputStream();
		OutputStreamWriter writer = new OutputStreamWriter(jsonGeneratorOutputBytes, "Shift_JIS");
		JsonGenerator jsonGenerator = mapper.getFactory().createGenerator(writer);
		mapper.writeValue(jsonGenerator, bean);
		assertThat(jsonGeneratorOutputBytes.toByteArray(), equalTo(writeExpected.getBytes("Shift_JIS")));

		// 「writeTree」の第一引数は、「JsonGenerator」のため、
		// ことエンコーディングに関しては「writeValue」において「JsonGenerator」を使用する時と同じ要領で使用します
		ByteArrayOutputStream jsonGeneratorOutputBytesForTree = new ByteArrayOutputStream();
		OutputStreamWriter writerForTree = new OutputStreamWriter(jsonGeneratorOutputBytesForTree, "Shift_JIS");
		JsonGenerator jsonGeneratorForTree = mapper.getFactory().createGenerator(writerForTree);
		JsonNode tree = mapper.readTree(writeExpected);
		mapper.writeTree(jsonGeneratorForTree, tree);
		assertThat(jsonGeneratorOutputBytesForTree.toByteArray(), equalTo(writeExpected.getBytes("Shift_JIS")));
	}
```

# Appendix
#### 1.「DataOutput」「DataInput」を引数に取るメソッド

- 「DataOutput」とは
    - プリミティブ型や文字列をバイトデータとして書き出す機能を提供
    - 文字列は「modified UTF-8」という形式で出力される(*7)
- 「DataInput」とは
    - DataOutput等で書き出されたバイトデータをプリミティブ型や文字列として読み出す機能を提供

まずは、一旦jacksonを離れて、「DataOutput」「DataInput」そのものの使用方法を確認します。
以下のように使用します(*8)。

``` java

	/*
	 * DataInput / DataOutputの典型的な使用例 
	 * ・DataOutput : プリミティブ型または文字列を直接バイトデータとして書き出すために使用する
	 * ・DataInput : DataOutput等で書き出されたバイトデータを読み出すために使用する
	 */
	@Test
	public void personShouldBeSerializedByDataOutputAndDeserializedByDataInput() throws Exception {

		Person person = new Person();
		person.setAge(25);
		person.setName("𠮷太郎");
		person.setKanaCode('あ');
		File file = new File("sample_dataouput.dat");

		// DataOutputによるバイトデータの書き出し
		DataOutput dout = new DataOutputStream(new FileOutputStream(file));
		dout.writeInt(person.getAge());
		dout.writeUTF(person.getName());// 「modified UTF-8」で出力
		dout.writeChar(person.getKanaCode());

		// DataInputによるバイトデータの読み出し
		DataInput din = new DataInputStream(new FileInputStream(file));
		Person deserializedPerson = new Person();
		deserializedPerson.setAge(din.readInt());
		deserializedPerson.setName(din.readUTF());// 「modified UTF-8」として読み出す
		deserializedPerson.setKanaCode(din.readChar());

		assertThat(deserializedPerson.toString(), equalTo(person.toString()));
	}
```

jacksonにおける想定される使用方法については、公式のドキュメント等に記載は見つけられませんでした。
しかし、上記コードからの類推でいろいろと試した結果、下記のコードで想定した結果が得られました。

結論としては、以下のように使用すればよさそうです

- 「writeValue(DataOutput, ...)」でシリアライズしたなら、「readValue(DataInput, ...)」でデシリアライズ
- 「writeUTF」で書き出したなら、「readUTF」で読み出した後、「readValue(String, ...)」等でデシリアライズ


``` java

	/*
	 * ObjectMapperで、DataInput / DataOutputを使用する例
	 */
	@Test
	public void person2ShouldBeSerializedByDataOutputAndDeserializedByDataInput() throws Exception {

		Person2 person = new Person2();
		person.setAge(25);
		PersonName personName = new PersonName();
		personName.setFirstname("𠮷太郎");
		personName.setLastname("山田");
		person.setPersonName(personName);
		person.setKanaCode('あ');
		File file = new File("sample_dataouput2.dat");

		// DataOutputによるバイトデータの書き出し
		DataOutput dout = new DataOutputStream(new FileOutputStream(file));
		dout.writeInt(person.getAge());
		// PersonNameはJSONで書き出す
		mapper.writeValue(dout, person.getPersonName());
		dout.writeChar(person.getKanaCode());

		// DataInputによるバイトデータの読み出し
		DataInput din = new DataInputStream(new FileInputStream(file));
		Person2 deserializedPerson = new Person2();
		deserializedPerson.setAge(din.readInt());
		PersonName deserializedPersonName = mapper.readValue(din, PersonName.class);
		// 以下のように記述しても正しくDeserializeできませんでした
		// PersonName deserializedPersonName = mapper.readValue(din.readUTF(),
		// PersonName.class);
		deserializedPerson.setPersonName(deserializedPersonName);
		deserializedPerson.setKanaCode(din.readChar());

		assertThat(deserializedPerson.toString(), equalTo(person.toString()));
	}

	/*
	 * ObjectMapperで、DataInput / DataOutputを使用する場合の例 
	 * 「writeUTF」で書き出されたJSONは
	 *  ObjectMapper#readValue(DataInput, Class<T>) <T>: T
	 * では正しくDeserializeできない !!! このテストは失敗します
	 */
	@Test
	public void modifiedUTF8StringShouldBeDeserializedByDataInput() throws Exception {

		File file = new File("sample_dataouput3.dat");

		// DataOutputによるバイトデータの書き出し
		DataOutput dout = new DataOutputStream(new FileOutputStream(file));
		dout.writeInt(55);
		dout.writeUTF("{\"firstname\":\"𠮷太郎\", \"lastname\":\"山田\"}");
		dout.writeChar('あ');

		// DataInputによるバイトデータの読み出し
		DataInput din = new DataInputStream(new FileInputStream(file));
		assertThat(55, equalTo(din.readInt()));
		PersonName deserializedPersonName = mapper.readValue(din, PersonName.class);
		// 以下のようにすると正しくDeserializeされました
		// PersonName deserializedPersonName = mapper.readValue(din.readUTF(),
		// PersonName.class);
		assertThat(deserializedPersonName.getFirstname(), equalTo("𠮷太郎"));
		assertThat(deserializedPersonName.getLastname(), equalTo("山田"));
		assertThat(din.readChar(), equalTo('あ'));
	}
	// 「1.「DataOutput」「DataInput」を引数に取るメソッド」のサンプルソースで使用するクラス
	// setter, getter, toStringは割愛。toStringの結果の比較でフィールド値が比較できるようになっています。
	static class Person2 {
		private int age;
		private PersonName personName;
		private char kanaCode;
	}

	static class PersonName {
		private String firstname;
		private String lastname;
	}

	static class Person {
		private int age;
		private String name;
		private char kanaCode;
	}

```

#### 2. JsonParser, JsonGeneratorについての補足
「JsonParser」「JsonGenerator」は当記事で対象としている「jackson-databind」ではなく「jackson-core」に位置付けられているクラスです(*9)。
「jackson-core」には(その名の通り）コアな低レベルのparser/generatorが含まれており、「jackson-databind」は「jackson-core」に依存しています。
コアな低レベルのparser/generatorが、ここで紹介する「JsonParser」「JsonGenerator」に当たります。

記事本文ではObjectMapperのメソッドに対象を絞っていたため、「JsonParser」「JsonGenerator」自体の使い方は紹介していませんでした。
以下で、エンコーディングを指定して「JsonParser」「JsonGenerator」を使用する例を紹介します。

``` java

	/*
	 * JsonParserに関する補足 
	 * UTF-8以外のエンコーディングを使用し、 
	 * 典型的なJsonParserの使い方でJSONをreadする例です
	 */
	@Test
	public void jsonParserShouldReadJSONIncrementally() throws Exception {

		Reader reader = new InputStreamReader(new ByteArrayInputStream(json.getBytes("Shift_JIS")), "Shift_JIS");
		JsonParser jsonParser = mapper.getFactory().createParser(reader);

		JsonToken t = null;
		String fieldName = null;
		String message = null;
		while ((t = jsonParser.nextToken()) != null) {
			if (t == JsonToken.FIELD_NAME) {
				fieldName = jsonParser.getCurrentName();
			}
			if (t == JsonToken.VALUE_STRING) {
				message = jsonParser.getText();
			}
		}
		assertThat(fieldName, equalTo("message"));
		assertThat(message, equalTo("あいうえお"));
	}

	/*
	 * JsonGeneratorに関する補足
	 * UTF-8以外のエンコーディングを使用し、
	 * 典型的なJsonGeneratorの使い方でJSONをwriteする例です。
	 */
	@Test
	public void jsonGeneratorShouldWriteJSONIncrementally() throws Exception {

		ByteArrayOutputStream outputBytes = new ByteArrayOutputStream();
		Writer writer = new OutputStreamWriter(outputBytes, "Shift_JIS");
		JsonGenerator jsonGenerator = mapper.getFactory().createGenerator(writer);
		jsonGenerator.writeStartObject();
		jsonGenerator.writeStringField("message", "あいうえお");
		jsonGenerator.writeEndObject();
		jsonGenerator.close();

		assertThat(outputBytes.toByteArray(), equalTo("{\"message\":\"あいうえお\"}".getBytes("Shift_JIS")));
	}
```





# 注釈および参考サイト
[RFC 4627](https://www.ietf.org/rfc/rfc4627.txt)(2019/5/3閲覧)
[RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.txt)(2019/5/3閲覧)

*1
jacksonの使い方については、少し昔の記事ですが、[Jackson使い方メモ](https://qiita.com/opengl-8080/items/b613b9b3bc5d796c840c)(2019/5/3閲覧)が参考になります。
jackson-databindのGitHubにおける「[Use It!](https://github.com/FasterXML/jackson-databind#use-it)」(2019/5/3閲覧)も参考になります。

*2
実際に動作させて確認した結果および、「[JsonEncoding.java](https://github.com/FasterXML/jackson-core/blob/master/src/main/java/com/fasterxml/jackson/core/JsonEncoding.java)」(2019/5/3閲覧)のjavadocの記述を根拠としています。

*3
[RFC 4627](https://www.ietf.org/rfc/rfc4627.txt)の「3.  Encoding」にはBOMに関する記述はありませんが、BOMは無視して判断する前提で記述されていると考えられます。

*4
「[JsonEncoding.java](https://github.com/FasterXML/jackson-core/blob/master/src/main/java/com/fasterxml/jackson/core/JsonEncoding.java)」(2019/5/3閲覧)に以下の記述があります。
「 Note: if application want to explicitly disregard Encoding limitations (to read in JSON encoded using an encoding not listed as allowed), they can use {@link java.io.Reader} / {@link java.io.Writer} instances as input」

*5
デモ用ソースコードの「(a)」の部分にも記載されている通り、Shift_JIS等でエンコードされたデータを「read」するとエラーとなる場合があります。

*6
「ObjectMapper#writeValue(OutputStream, Object): void」のjavadocには明記されています

*7
「[DataInput](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/DataInput.html)」(2019/5/4閲覧)のjavadoc参照。

*8
「[KAB-studio > プログラミング > JavaA2Z > DataInputStream](http://www.kab-studio.biz/Programing/JavaA2Z/Word/00000606.html)」(2019/5/4閲覧)を参考にしました。

*9
「[jackson-core](https://github.com/FasterXML/jackson-core)」(2019/5/4閲覧)のGitHubの記述を参照しました。