先日 Java + Spring Fraework で、サーバ/クライアント間の通信についてまとめました。

[Java][Spring] クライアントと JSON で通信するためのバックエンド実装
SpringFrameworkでJSON形式でクライアントと通信する方法をまとめてみました。
atuweb 開発ブログ

上記ではさらっとしか触れなかった、特殊文字の Unicode シーケンス 変換について整理いたしました。

Overview

何をするのか

Jackson を利用した Json、Object のコンバートで特殊文字をエスケープする。

Cocos2d-x で実装したクライアントとの通信で、クライアント側 C++ の JSON パーサーでは特殊文字の扱いが苦手であったため、特殊文字をエスケープすることで回避したのでした。

開発環境

  • Java 7
  • Spring 4
  • Maven 3
  • jackson 2.4

数年前の実装のため Java 7 です。
新しいプロジェクトでは最新のバージョンをご利用ください。

エスケープ挙動

この記事で JSON にコンバートするオブジェクトの構造です。

1@Data
2public class ResponsetBean implements Serializable {
3    private static final long serialVersionUID = 1L;
4
5    private Integer missionId;
6    private String  missionName;
7
8    private Integer difficulty;
9}

上記 POJO のインスタンスを生成し、値を入れておきます。

1ResponsetBean response = new ResponsetBean();
2response.setMissonId(1188);
3response.setMissionName("時のはざま");
4response.setDifficulty(8);

エスケープなし

New した ObjectMapper をそのまま利用する、通常の挙動です。

実行コード

1try {
2    ObjectMapper mapper = new ObjectMapper();
3    String json = mapper.writeValueAsString(response);
4} catch (Exception e) {
5    throw new RuntimeException(e);
6}

得られる JSON 文字列

1{"missionId":1188,"missionName":"時のはざま","difficulty":8}

マルチバイト文字をエスケープ

マルチバイト文字をエスケープしましょう。
マッパー設定に Feature.ESCAPE_NON_ASCII 、true を設定します。

実行コード

1try {
2    ObjectMapper mapper = new ObjectMapper();
3    mapper.configure(Feature.ESCAPE_NON_ASCII, true);
4    String json = mapper.writeValueAsString(response);
5} catch (Exception e) {
6    throw new RuntimeException(e);
7}

得られる JSON 文字列

1{"missionId":1188,"missionName":"\u6B21\u5143\u306E\u306F\u3056\u307E","difficulty":8}

マルチバイト文字がエスケープされ、 Unicode シーケンス化されていることが分かります。

特殊文字をエスケープ

さらなる敵

ここでさらなる敵が、、、
なんと裏ボスが覚醒しました。

ミッション名「次元のはざま」を「次元のはざま/“覚醒”」に変化します。

インスタンスに与える文字を変更します。

1response.setMissionName("次元のはざま/\"覚醒\"");

上のパーサに与えると、次の JSON 文字列が得られます。

1{"missionId":1188,"missionName":"\u6B21\u5143\u306E\u306F\u3056\u307E/\"\u899A\u9192\"","difficulty":8}

なかなか正規表現が難しそうな形になりましたね。

そこで、エスケープを拡張します。

ObjectMapper に与える、 特殊エスケープのマッピング を定義したエスケープ拡張クラスを作成します。
次にコードを示します。

エスケープ拡張クラス

 1import com.fasterxml.jackson.core.SerializableString;
 2import com.fasterxml.jackson.core.io.CharacterEscapes;
 3
 4@SuppressWarnings("serial")
 5public class CustomCharacterEscapes extends CharacterEscapes {
 6
 7    private final int[] asciiEscapes;
 8
 9    public CustomCharacterEscapes()
10    {
11        int[] esc = CharacterEscapes.standardAsciiEscapesForJSON();
12        esc['"']  = CharacterEscapes.ESCAPE_STANDARD;
13        esc['\''] = CharacterEscapes.ESCAPE_STANDARD;
14        esc['/']  = CharacterEscapes.ESCAPE_STANDARD;
15        esc['\n'] = CharacterEscapes.ESCAPE_STANDARD;
16        asciiEscapes = esc;
17    }
18
19    @Override
20    public int[] getEscapeCodesForAscii() {
21        return asciiEscapes;
22    }
23
24    // no further escaping (beyond ASCII chars) needed:
25    @Override
26    public SerializableString getEscapeSequence(int ch) {
27        return null;
28    }
29}

マルチバイト文字、および次の文字を Unicode シーケンス にコンバートします。

  • ” (ダブルクォーテーション)
  • ’ (シングルクォーテーション)
  • / (スラッシュ)
  • \n (改行コード)

エスケープしたい特殊文字を追加する場合、変数 esc に追加していってください。

実行コードと結果

実行コード
1try {
2    ObjectMapper mapper = new ObjectMapper();
3    mapper.configure(Feature.ESCAPE_NON_ASCII, true);
4    mapper.getFactory().setCharacterEscapes(new CustomCharacterEscapes());
5    String json = mapper.writeValueAsString(response);
6} catch (Exception e) {
7    throw new RuntimeException(e);
8}

2018年11月更新
上記に @Deprecated な getJsonFactory() を使っていたところ、「 getFactory() が使える」と教えてもらいました。
ありがとうございます。

得られる JSON 文字列
1{"missionId":1188,"missionName":"\u6B21\u5143\u306E\u306F\u3056\u307E\u002F\u0022\u899A\u9192\u0022","difficulty":8}

ダブルクォーテーション、スラッシュともに Unicode シーケンスに変換されていることが確認できました。

おわりに

今回のサンプルはオブジェクトから JSON へのコンバートのみです。 JSON からオブジェクトへバインディングする際も同様のコードで実装できます。

楽しい開発ライフを過ごしてください。


やさしいJava 第6版 (「やさしい」シリーズ)

高橋 麻奈
出版社:SBクリエイティブ  発売日:2016-08-31

Amazonで詳細を見る