JavaScript Primerのスポンサーを募集中

JSON

この章では、JavaScriptと密接な関係にあるJSONというデータフォーマットについて見ていきます。

JSONとは

JSONはJavaScript Object Notationの略で、JavaScriptのオブジェクトリテラルをベースに作られた軽量なデータフォーマットです。JSONの仕様はECMA-404として標準化されています。JSONは、人間にとって読み書きが容易で、マシンにとっても簡単にパースや生成を行える形式になっています。そのため、多くのプログラミング言語がJSONを扱う機能を備えています。

JSONはJavaScriptのオブジェクトリテラル、配列リテラル、各種プリミティブ型の値を組み合わせたものです。ただしJSONとJavaScriptは一部の構文に違いがあります。たとえばJSONでは、オブジェクトリテラルのキーを必ずダブルクォートで囲まなければいけません。また、小数点から書きはじめる数値リテラルや、先頭がゼロからはじまる数値リテラルも使えません。これらは機械がパースしやすくするために仕様で定められた制約です。

{"object": {"number":1,"string":"js-primer","boolean":true,"null":null,"array": [1,2,3]    }}

JSONの細かい仕様に関してはjson.orgの日本語ドキュメントにわかりやすくまとまっているので、参考にするとよいでしょう。

JSONオブジェクト

JavaScriptでJSONを扱うには、ビルトインオブジェクトであるJSONオブジェクトを利用します。JSONオブジェクトはJSON形式の文字列とJavaScriptのオブジェクトを相互に変換するためのparseメソッドとstringifyメソッドを提供します。

JSON文字列をオブジェクトに変換する

JSON.parseメソッドは引数に与えられた文字列をJSONとしてパースし、その結果をJavaScriptのオブジェクトとして返す関数です。次のコードは簡単なJSON形式の文字列をJavaScriptのオブジェクトに変換する例です。

// JSONはダブルクォートのみを許容するため、シングルクォートでJSON文字列を記述const json ='{ "id": 1, "name": "js-primer" }';const obj =JSON.parse(json);console.log(obj.id);// => 1console.log(obj.name);// => "js-primer"

文字列がJSONの配列を表す場合は、JSON.parse静的メソッドの返り値も配列になります。

const json ="[1, 2, 3]";console.log(JSON.parse(json));// => [1, 2, 3]

与えられた文字列がJSON形式でパースできない場合は例外が投げられます。また、実際のアプリケーションでJSONを扱うのは、外部のプログラムとデータを交換する用途がほとんどです。外部のプログラムが送ってくるデータが常にJSONとして正しい保証はありません。そのため、JSON.parse静的メソッドは基本的にtry...catch構文で例外処理をするべきです。

const userInput ="not json value";try {const json =JSON.parse(userInput);}catch (error) {console.log("パースできませんでした");}

オブジェクトをJSON文字列に変換する

JSON.stringifyメソッドは第一引数に与えられたオブジェクトをJSON形式の文字列に変換して返す関数です。HTTP通信でサーバーにデータを送信するときや、アプリケーションが保持している状態を外部に保存するときなどに必要になります。次のコードはJavaScriptのオブジェクトをJSON形式の文字列に変換する例です。

const obj = {id:1,name:"js-primer",bio:null };console.log(JSON.stringify(obj));// => '{"id":1,"name":"js-primer","bio":null}'

JSON.stringify静的メソッドにはオプショナルな引数が2つあります。第二引数はreplacer引数とも呼ばれ、関数あるいは配列を渡せます。関数を渡した場合は引数にプロパティのキーと値が渡され、その返り値によって文字列に変換される際の挙動をコントロールできます。次の例は値がnullであるプロパティを除外してJSONに変換するreplacer引数の例です。replacer引数の関数でundefinedが返されたプロパティは、変換後のJSONに含まれなくなります。

const obj = {id:1,name:"js-primer",bio:null };constreplacer = (key, value) => {if (value ===null) {returnundefined;    }return value;};console.log(JSON.stringify(obj, replacer));// => '{"id":1,"name":"js-primer"}'

replacer引数に配列を渡した場合はプロパティの許可リストとして使われ、その配列に含まれる名前のプロパティだけが変換されます。

const obj = {id:1,name:"js-primer",bio:null };const replacer = ["id","name"];console.log(JSON.stringify(obj, replacer));// => '{"id":1,"name":"js-primer"}'

第三引数はspace引数とも呼ばれ、変換後のJSON形式の文字列を読みやすくフォーマットする際のインデントを設定できます。数値を渡すとその数値分の長さのスペースで、文字列を渡すとその文字列でインデントされます。次のコードはスペース2個でインデントされたJSONを得る例です。

const obj = {id:1,name:"js-primer" };// replacer引数を使わない場合はnullを渡して省略するのが一般的ですconsole.log(JSON.stringify(obj,null,2));/*{   "id": 1,   "name": "js-primer"}*/

また、次のコードはタブ文字でインデントされたJSONを得る例です。

const obj = {id:1,name:"js-primer" };console.log(JSON.stringify(obj,null,"\t"));/*{   "id": 1,   "name": "js-primer"}*/

JSONにシリアライズできないオブジェクト

JSON.stringify静的メソッドはJSONで表現可能な値だけをシリアライズします。そのため、値が関数やSymbol、あるいはundefinedであるプロパティなどは変換されません。ただし、配列の値としてそれらが見つかったときには例外的にnullに置き換えられます。またキーがSymbolである場合にもシリアライズの対象外になります。代表的な変換の例を次の表とサンプルコードに示します。

シリアライズ前の値シリアライズ後の値
文字列・数値・真偽値対応する値
nullnull
配列配列
オブジェクトオブジェクト
関数変換されない(配列のときはnull)
undefined変換されない(配列のときはnull)
Symbol変換されない(配列のときはnull)
RegExp{}
Map, Set{}
BigInt例外が発生する

// 値が関数のプロパティconsole.log(JSON.stringify({x:function() {} }));// => '{}'// 値がSymbolのプロパティconsole.log(JSON.stringify({x:Symbol("") }));// => '{}'// 値がundefinedのプロパティconsole.log(JSON.stringify({x:undefined }));// => '{}'// 配列の場合console.log(JSON.stringify({x: [10,function() {}] }));// => '{"x":[10,null]}'// キーがSymbolのプロパティJSON.stringify({ [Symbol("foo")]:"foo" });// => '{}'// 値がRegExpのプロパティconsole.log(JSON.stringify({x:/foo/ }));// => '{"x":{}}'// 値がMapのプロパティconst map =newMap();map.set("foo","foo");console.log(JSON.stringify({x: map }));// => '{"x":{}}'

オブジェクトがシリアライズされる際は、そのオブジェクトの列挙可能なプロパティだけが再帰的にシリアライズされます。RegExpMapSetなどのインスタンスは列挙可能なプロパティを持たないため、空のオブジェクトに変換されます。

また、JSON.stringify静的メソッドがシリアライズに失敗することもあります。よくあるのは、参照が循環しているオブジェクトをシリアライズしようとしたときに例外が投げられるケースです。たとえば次の例のように、あるオブジェクトのプロパティを再帰的にたどって自分自身が見つかるような場合はシリアライズが不可能となります。JSON.parse静的メソッドだけでなく、JSON.stringify静的メソッドも例外処理を行って安全に使いましょう。

const obj = {foo:"foo" };obj.self = obj;try {JSON.stringify(obj);}catch (error) {console.error(error);// => "TypeError: Converting circular structure to JSON"}

toJSONメソッドを使ったシリアライズ

オブジェクトがtoJSONメソッドを持っている場合、JSON.stringify静的メソッドは既定の文字列変換ではなくtoJSONメソッドの返り値を使います。次の例のように、引数に直接渡されたときだけでなく引数のプロパティとして登場したときにも再帰的に処理されます。

const obj = {foo:"foo",toJSON() {return"bar";    }};console.log(JSON.stringify(obj));// => '"bar"'console.log(JSON.stringify({x: obj }));// => '{"x":"bar"}'

toJSONメソッドは自作のクラスを特殊な形式でシリアライズする目的などに使われます。

まとめ

この章では、JSONについて学びました。

  • JSONはJavaScriptのオブジェクトリテラルをベースに作られた軽量なデータフォーマット
  • JSONオブジェクトを使ったシリアライズとデシリアライズ
  • JSON形式にシリアライズできないオブジェクトもある
  • JSON.stringifyはシリアライズ対象のtoJSONメソッドを利用する