プログラミング言語SML#解説 4.0.0版
13 SML#の拡張機能:動的型付け機構とJSONの型付き操作

13.6 JSONプログラミング例

以下の対話セッションは,MLのレコードのリストに対応する 構造を持つJSONを読み込む例です.

# val J = "[{n̈ame:̈J̈oe,̈ äge:̈21, g̈rade:̈1.1},"
            ^ "{n̈ame:̈S̈ue,̈ äge:̈31, g̈rade:̈2.0},"
            ^ "{n̈ame:̈B̈ob,̈ äge:̈41, g̈rade:̈3.9}]";
val J =
  "[{"name":"Joe", "age":21, "grade":1.1},
    {"name":"Sue", "age":31, "grade":2.0},
    {"name":"Bob", "age":41, "grade":3.9}]" : string
# fun getNames l = map #name l;
val getNames = fn : [’a#{name: ’b}, ’b. ’a list -> ’b list]
# val j = Dynamic.fromJson J;
val j = _ : Dynamic.void Dynamic.dyn
# val vl = _dynamic j as {name:string, age:int, grade:real} list;
val vl =
  [
   {age = 21,grade = 1.1,name = "Joe"},
   {age = 31,grade = 2.0,name = "Sue"},
   {age = 41,grade = 3.9,name = "Bob"}
  ] : {age: int, grade: real, name: string} list
# val nl = getNames vl;
val nl = ["Joe","Sue","Bob"] : string list

より複雑な例を見てみましょう. 以下のようなJSONを考えます.

[
  {"name":"Alice", "age":10, "nickname":"Allie"},
  {"name":"Dinah", "age":3, "grade":2.0},
  {"name":"Puppy", "age":7}
]

このJSON文字列が変数Jに束縛されているとします. これはヘテロジニアスなリストです. このようなリストは,直接MLのリストにキャストすることができません. 一方,このリストを,各要素の共通部分に注目し, 「少なくともnameageフィールドを持つレコードのリスト」 と見ることはできます. この見方に基づき,以下のようにしてJを読み込みます.

val j = Dynamic.fromJson J
val vl = _dynamic j as {name:string, age:int} Dynamic.dyn list

vlの型は {name:string, age:int} Dynamic.dyn listです. このリストの静的なビューは,

val l = map Dynamic.view vl

として取り出すことができます. さらに,各要素からnameフィールドの値を取り出したければ,

val names = map #name l

とします.

リストの各要素に対して,あるフィールドの有無をチェックしたい 場合は,個々の要素に対して動的検査を行います. 例えば,nicknameがある場合はそれを,そうでなければnameを 取り出したい場合は,以下のように書きます.

fun getFriendlyName x =
    _dynamiccase x of
      {nickname = y:string, ...} => y
    | _ : Dynamic.void Dynamic.dyn => #name (Dynamic.view x)
val friendlyNames = map getFriendlyName vl

このgetFriendlyName関数の型は, [’a#reify#{name : string}. ’a Dynamic.dyn -> string]です. 多相レコード型と動的レコード型が組み合わさっていることに 注意してください. これは,JSONを処理する観点からは,直感的には {name : string} Dynamic.dyn -> stringと同様の意味を持つように 見えますが, これらの厳密な意味は異なります. 後者は, name以外のフィールドの存在は動的型検査しなければ分からないものだけ しか引数に取れないのに対し, 前者はname以外のフィールドの存在が確認されているものも引数に 取ることができます.