【JavaScript】JSON

# 概要
JavaScriptを学習、理解を深めるため「[JavaScript Primer 迷わないための入門書](https://jsprimer.net/)」を読み、
理解した内容等を記載していく。

「[【JavaScript】JavaScript入門一覧](https://qiita.com/andota05/items/22fae452225b785fd405)」に他の記事をまとめています。

# この記事で理解できること
* JSONとは
* JSONで扱える値と型
* JSONオブジェクトの関連メソッド等の使用方法

# JSON(JavaScript Object Notation)とは
* JavaScriptの`オブジェクトリテラルをベース`に作られた軽量なデータフォーマット。
* 多くのプログラミング言語がJSONを扱う機能を備えている。
* JSONでは、オブジェクトリテラルのキーを必ず`ダブルクォート`で囲む必要がある。

## JSONで値として扱える型
|型|例|備考|
|--|--|--|
|文字列|文字列|
|数値|1、1.234|小数点から始まる or 0から始まるリテラルは×<br />NGな例:「.1」「01234」|
|null|null|
|真偽値|true、false|
|オブジェクト|{ }||
|配列|[ ]|

```json
{
  "string": "文字列",
  "number": 123,
  "null": null,
  "bool": true,
  "obj": { "sub":
            { "sub_obj":
              { "key": "value" }
            }
          },
  "array": ["A", "B", "C"]
}
```

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

## parseメソッド
* `JSON.parseメソッド`は引数に与えられた文字列をJSONとしてパースし、結果を`JavaScriptのオブジェクト`として返す。
* JSON形式でパースできない場合は、例外が投げられるため`try...catch`構文で例外を処理することが推奨される。

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

console.log(obj);       // => { id: 1, name: 'taro' }
console.log(obj.id);    // => 1
console.log(obj.name);  // => taro
```

## stringifyメソッド
* `JSON.stringifyメソッド`は、`第一引数に与えられたオブジェクト`をJSON形式の文字列に変換して返す関数。
* HTTP通信で`サーバーにデータを送信`する時や、 アプリケーションが保持している状態を`外部に保存`する時などに有用。
* stringifyメソッドにはオプショナルな引数が存在し、第二引数は`replacer引数`、第三引数は`space引数`とも呼ばれる。

```js
const obj = { id: 1, name: "taro", hobby: null };
console.log(JSON.stringify(obj)); // => {"id":1,"name":"taro","hobby":null}
```
## replacer引数
* `関数`あるいは`配列`を渡すことができる。

### 関数を渡した場合
* 関数の引数にプロパティの`キーと値`に渡され、その返り値によって`文字列に変換される際の挙動をコントロール`できる。
* replacer引数の関数で値に`undefined`が返されたプロパティは、変換後のJSONに含まれなくなる。

```js
// プロパティ「hobby」の値が"baseball"の場合は除外する関数をreplacer引数として渡した例
const obj = { id: 1, name: "Taro", hobby: "baseball" };

const replacer = (key, value) => {
  if (value === "baseball") {
    // valueが"baseball"の場合はundefinedを返す
    return undefined;
  }
  
  return value;
};

const json = JSON.stringify(obj, replacer);
console.log(json); // => {"id":1,"name":"Taro"}
```

### 配列を渡した場合
* プロパティの`ホワイトリスト`として使われ、 `その配列に含まれる名前のプロパティだけ`が変換される。

```js
// 配列(ホワイトリスト)にプロパティ「id」「name」を指定し、replacer引数として渡した例
const obj = { id: 1, name: "Taro", hobby: "baseball" };

const replacer = ["id", "name"];
const json = JSON.stringify(obj, replacer);
console.log(json); // => {"id":1,"name":"Taro"}
```

## space引数
* 変換後のJSON形式の文字列を読みやすくフォーマットする際の`インデント`を設定できる。

```js
const obj = { id: 1, name: "Taro", hobby: "baseball" };
const replacer = ["id", "name"];

// インデント6つでフォーマット
const json_indent6 = JSON.stringify(obj, replacer, 6);
console.log(json_indent6);
// {
//       "id": 1,
//       "name": "Taro"
// }

// 第二引数(replacer)は無しでインデント2つでフォーマット
const json_indent2 = JSON.stringify(obj, null, 2);
console.log(json_indent2);
// {
//   "id": 1,
//   "name": "Taro",
//   "hobby": "baseball"
// }

// 第二引数(replacer)は無しでタブでフォーマット
const json_tab = JSON.stringify(obj, null, "\t");
console.log(json_tab);
// {
// 	 "id": 1,
// 	 "name": "Taro",
// 	 "hobby": "baseball"
// }
```

# toJSONメソッドを使ったシリアライズ
* あるオブジェクトが`toJSONメソッド`を持っている場合、`JSON.stringifyメソッド`は既定の文字列変換ではなく`toJSONメソッド`の返り値を使用する。
* 引数に直接渡されたときだけでなく`引数のプロパティ`として登場したときにも`再帰的に`処理される。
* 自作のクラスを特殊な形式でシリアライズする目的などに使われる。

```js
// toJSONメソッドを保有しないオブジェクト
const obj = { 
  id: 1,
  name: "Taro",
  hobby: "baseball",
};

// toJSONメソッドを保有するオブジェクト
const hasToJsonMethodObj = { 
  id: 1,
  name: "Taro",
  hobby: "baseball",
  toJSON() {
    return "has toJSON method!!"
  }
};

console.log(JSON.stringify(obj));                // => {"id":1,"name":"Taro","hobby":"baseball"}
console.log(JSON.stringify(hasToJsonMethodObj)); // => "has toJSON method!!"

// オブジェクトリテラルでtoJSONメソッドを持つオブジェクトを表現し、引数として渡してみる
console.log(JSON.stringify({ name: "Taro", toJSON() { return "has toJSON method!!" }}));
// => "has toJSON method!!"
```