必見の記事

非エンジニアに贈る「具体例でさらっと学ぶJSON」

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

よく訓練されたアップル信者、都元です。「JSONってなんとなくわかるけど、構造を読み取れる自信はないし、ましてや書ける気はまったくしない。」そんな人に贈るエントリーです。残念ながら、「13日の金曜日」や「Why Japanese people!!」しか思い浮かばない人は、想定読者対象外ですのでお引き取りください。逆に、正確な定義が欲しい人はRFC 4627RFC 7159(アップデートされてました)をご確認ください。

「JSON」とは。

JSONとは JavaScript Object Notation の略で、要するに「JavaScriptの中でオブジェクトを記述する書式」のことです。と言われてもなんのことだか分からない人向けの説明なので安心してください。

JSONというのは「データを表現するための記法(≒文法)」です。シンプルなデータであれば、文法など気にせずにただ書けば問題ありません。少々複雑でも、我々は「表」という記法を知っているため、ある程度の複雑さには対応できます。しかし、表で表すのが困難な程度の複雑な構造を持つデータを表現しなければならないことは多々あります。

データを表現する目的は、誰か *1データを伝えることです。相手が人間であれば、注意書きや複数の「表」を駆使してデータを伝えられますが、相手がコンピュータである場合、その記法は厳密なルールの上に記述しなければ、伝わりません。

このように、表形式では表現が困難な構造のデータを、人間に対するある程度の可読性を残しつつ、コンピュータに対しても伝達できるような記法、その1つがJSONです。そしてJSON形式で記述したデータのことを、JSON-textといいます。

以下、ざっくりとボトムアップ(細かいことを先に理解して、最後に全体を理解)で説明します。

「値(value)」とは

まず本稿における「値」という言葉を確認しておきましょう。難しく言えば「JavaScriptにおいて、代入文の右辺に記述できる、リテラルのみで構成された記述」としたいんですが、下記に分かりやすく具体例で説明します。

JSONの世界には下記6種類の「値」が存在します。下記に示した記述は全て「値」です。ちなみにリストの上4つを「単純値」、リストの下2つを「複合値」と呼ぶことにします。(単純値と複合値という言葉は本稿での造語なので注意)

  • 文字列値(string):"abc""def"等、任意の情報をダブルクオートで囲んだ記述。
  • 数値(number):123.45等、数値をそのまま書いた記述。
  • 真偽値(boolean):true及びfalseという「yes/no的な意味合い」を表現する特別な記述。
  • ヌル値(null):nullという「値がカラッポであること」を表現する特別な記述。
  • 配列値(array):[][1][1,true]等、0〜複数個の値の並びを表現する記述。
  • オブジェクト値(object):{}{"foo":"bar"}{"foo":"bar", "baz":null}。これは説明が複雑なので後回し。

一方、下記は「値」ではありません。

  • abcd"ef""ab(全体がクオートされていなかったり閉じていない。)
  • TRUENULL(大文字と小文字は区別する。)
  • [1][2,3][1],[2,3]{}{}(複数の値がつながったものは値ではない。)

一般的に「値」というと多くの人は単純値の方だけを頭にうかべます。ここでは、「今まで漠然と値だと思ってたものは単純値と呼ぶんだね」、「値っていうものを厳密に考えると、(理解しづらいけど)複合値ってのも含むんだね」ってことを受け入れてくれれば大丈夫です。

「メンバー(member)」とは

次に「メンバー」という言葉です。これは、「コロン(:)を、文字列値と値ではさんだもの」とします。コロンの左辺が文字列値です。下記は全て「メンバー」です。

  • "foo": "hoge"
  • "bar":"fuga"
  • "baz":1
  • "qux":[]

一方、下記は「メンバー」ではありません。

  • 1:2(左辺が文字列値じゃない。)
  • "foo";"bar""foo"1(コロンがない。)
  • "foo":"bar":"baz"(コロンが2つ以上ある。)
  • "foo":hoge(右辺が値じゃない。)

メンバー構成要素のうち、コロンの左側を「名前(name) *2」と呼びます。コロンの右側は、その名前に対する「値」です。

ちなみに余談ですが、値であるものはメンバーではなく、メンバーであるものは値じゃない、排他的な関係にありますね。

配列値の書式詳細

さて、上では少々さらっと流してしまった配列値について。

配列値というのは[ ... ]という形式で、"..."部分にはカンマ区切りの「値」を0個以上含むものです。配列内のそれぞれの値のことを「要素(element)」と呼びます。それぞれの要素の種類は一致している必要はありません。値ならなんでもいいので、配列の中に配列値が入っていても、配列の中にオブジェクト値が入っていても構いません。配列値の例は下記の通りです。

  • []
  • [1]
  • [true]
  • ["foo", 23, null]
  • [1, {}, true, [[], 2, [3,4]]]

そして、配列値ではないものの例は下記。

  • 1, 2, 3{1, 2, 3}("[]"で囲まれていない)
  • [foo, 1](中身に値じゃないものが含まれてる)
  • ["foo" 23 null](カンマ区切りじゃない)

配列値は複数の要素から構成されていることがわかりました。そして、配列に対しては、「インデックス」と呼ばれる番号で、要素を取り出すことができます。例えば上で例に挙げた["foo", 23, null]ですが、「この配列のインデックス1の値は何?」という問に対しては23と答えられます。(最初の要素がインデックス0、2番目の要素がインデックス1です。0-basedなので1つズレます。)

配列は「中身の順序が大事」であることが多いです。

練習問題1:配列[1, {}, true, [[], 2, [3,4]]]の、インデックス3の値は何ですか?(回答は本稿末尾)

オブジェクト値の書式詳細

そして、上で後回しにしたオブジェクト値について。

オブジェクト値というのは{ ... }という形式で、"..."部分には、カンマ区切りの「メンバー」を0個以上含むものです。

  • {}
  • {"foo": "bar"}
  • {"foo": "bar", "baz": "qux"}
  • {"foo": "bar", "baz": {"foo": "bar", "baz": "qux"}}
  • {"foo": [1, null], "baz": {"foo": [true, "bar"], "baz": "qux"}}

ただし、一般的に、同じ名前を持つメンバーを複数持つことはありません。厳密には、同じ名前を持つメンバーが複数あっても違反ではないようなので、下記は一応オブジェクト値の一種ですが、一般的ではないと考えて構いません。

  • {"foo": "bar", "foo": "qux"}

そしてオブジェクト値ではないものの例がこちら。

  • "foo": "bar", "baz": "qux"["foo": "bar", "baz": "qux"]("{}"で囲まれていない)
  • {"hoge", "baz": "qux"}(中身にメンバーじゃないものが含まれている)
  • {"foo": "bar" "baz": "qux"}(カンマ区切りじゃない)

上で1セクション使って「メンバー」の構造を説明しましたが、メンバーというのはオブジェクト値の構成要素として登場するだけで、出番はここだけです。オブジェクト値の書き方を理解してしまえば、メンバーについては名前を忘れてしまっても構いません。

さて、オブジェクト値は0〜複数個のメンバーから構成されていることがわかりました。そして、オブジェクトに対しては「名前」で、要素を取り出すことができます。例えば上で例に挙げた{"foo": "bar", "baz": "qux"}ですが、「このオブジェクトの、名前"baz"に対応する値は何?」という問に対しては"qux"と答えられます。

一方、「このオブジェクトのインデックス1(つまり2番目)の要素の値は何?」という問に対しては答えられません。下記2つのオブジェクトは同じデータを表現している(等価)ので、どちらが2番目かは定めることができないからです。

  • {"foo": "bar", "baz": "qux"}
  • {"baz": "qux", "foo": "bar"}

オブジェクトは「中身に順序の概念は存在しない」のです。

練習問題2:オブジェクト{"foo": [1, null], "baz": {"foo": [true, "bar"], "baz": "qux"}}の、名前"baz"の値の、さらに名前"foo"の値の、さらにインデックス0の値は何ですか?(回答は本稿末尾)

「JSON-text」とは

さて、最終的に。「JSON-text」とは何でしょうか。複合値 *3を表現するテキストのことです。単純値を表現するものはJSON-textではありません。そして「値」でないものは全て、JSON-textではありません。

インデントと整形

ここまであまり触れませんでしたが、JSON-textの中では(1つの単純値を分断しない限り)スペースと改行を自由に挿入して構いません。上に例で挙げた

{"foo": [1, null], "baz": {"foo": [true, "bar"], "baz": "qux"}}

は読みづらいですが、下記のように人間に分かりやすいように改行とスペースを挿入してやると、配列の要素の関係や、メンバーを認識しやすくなります。

{
  "foo": [ 1, null ],
  "baz": {
    "foo": [ true, "bar" ],
    "baz": "qux"
  }
}

どちらも同じデータを表しており、どちらもJSON-textです。

なぜJSONを使うのか

世の中には様々な形を持ったデータがあります。あらゆるデータがExcelの表形式でわかりやすく表現できればいいのですが、そうもいきません。例えば、10個のEC2インスタンスがあったとして、それぞれについて、下記の情報を表現したいとします。

  • インスタンスID
  • 起動日時
  • AMIのID
  • ...

この辺りまでは良いでしょう。表形式で表現できますね。まだ続きがあります。

  • ...
  • EIP情報(1つとは限らない)
    • 1つ目のEIP
    • 2つ目のEIP
    • ...
  • タグ情報(もちろん1つとは限らない)...

こんな感じのデータになってくると、もう表形式が破綻してしまいます。表よりは読みづらいのは確かですが、こういった情報を表現できるのがJSONです。

[
  {
    "InstanceId": "i-XXXXXXXX",
    "ImageId": "ami-YYYYYYYY",
    "LaunchTime": "2015-05-28T08:30:10.000Z",
    "Tags": [
      {
        "Value": "portnoydev-emr",
        "Key": "Name"
      },
      {
        "Value": "j-ZZZZZZZZZZZZ",
        "Key": "aws:elasticmapreduce:job-flow-id"
      },
      {
        "Value": "CORE",
        "Key": "aws:elasticmapreduce:instance-group-role"
      }
    ]
  },
(略)
]

しかし、一回絶望するといい

最後に、下記もJSONの一種なんですよ。っていう現実を見て一回絶望してください。人間への可読性を残していると言いつつ、それはデータ次第だってこともわかりますね。

練習問題の回答

{
  "answers": {
    "練習問題1": "[[], 2, [3,4]]",
    "練習問題2": "true"
  }
}

脚注

  1. 未来の自分かもしれません。
  2. しばしば「キー(key)」と呼ぶこともあります。
  3. RFC 7159によるアップデートで、単純値もJSON-textの一種であるとされました。