JSONとサロゲートペア

[追記] この問題について Sagittarius では 0dd978805548 、 Gauche では 404ae55 で修正されました。[/追記]

文字コードとして UTF-8 を使っている限りはサロゲートペアのことを忘れられる。 そんな風に思っていた。 ところが、あるとき JSON のパースで問題が生じたので調査したところサロゲートペアが関係していた。 文字列中で使えるユニコードエスケープについての問題である。

さて、問題について説明する前に JSON の文字列がどのようになっているかを見てみよう。

f:id:SaitoAtsushi:20140417162041p:plain

ユニコードエスケープは 16 進数で 4 桁に固定なのである。 4 桁で表せない文字はどのように扱われるのだろうか。 例えば「𩸽」という文字のコードポイントは 16 進数で 29e3d であり、 5 桁であるのでそのままでは表せない。 このような場合、 JSON では UTF-16 のようにサロゲートペアにして見掛け上の二文字で表すようだ。 つまり「𩸽」は "\ud867\ude3d" とエンコードされる。

そしてこの場合をうまくパースできないパーサがあるというのが今回の問題だ。 具体的には Scheme 処理系の SagittariusJSON パーサでハマってしまった。

#!r6rs
(import (rnrs) (json))
(display (json-read (open-string-input-port "{\"\\ud867\\ude3d\":2}")))

これだと

*error*
#<condition
  &assertion
  &who integer->char
  &message code point out of range
  &irritants 55399

>
stack trace:
  [1] integer->char
  [2] (jstring-body loop)
... (後略)

という風なエラーになる。 \ud867 をひとつの文字として変換しようとするせいだ。

SagittariusJSON パーサは Chicken scheme からの移植とのことで、元々はユニコードエスケープに対応していなかった。 それを私が改良してパッチを送ったという経緯があり、要するにこれは私の配慮が不足していたという話である。 JSON の仕様が明確に要求しているわけでもないような微妙な感じということはあるのだが、実際に存在する事例なのでどうにかしたいと思う。 その内にパッチ書くつもり。

ちなみに MoshGauche でもきちんとパース出来なかった。

Document ID: 23f5522cac69847325f6ec22fb2285d8