Functionを保護してJSON.stringifyを行う

# はじめに

JavascriptでオブジェクトをJSON文字列に変換しようとした場合、`JSON.stringify()`を用いますが、`JSON.stringify()`は通常、オブジェクト内に含まれている関数をそのキーごと無視してしまいます。

その為下記のようなオブジェクトをJSON文字列化しようとすると...

```js
const object = {
    key1: 'string',
    key2: '\\',
    key3: 12345,
    key4: function() { return true; }
};

console.log(JSON.stringify(object));
```

こうなります。

```js
{"key1":"string","key2":"\\","key3":12345}
```

そう、`key4`ごと消滅してしまうのです。


# それでは困る

これだとオブジェクトを完全に文字列で表現することが出来ません。。
文字列も関数もなにもかも全て含めて文字列で表現したいのです。


# そこで

`JSON.stringify()`には`replacer`というものが渡せるようになっています。
`replacer`へは関数、または配列を渡すことが出来ます。

![](https://i.gyazo.com/a1df4bca30bbeba8dc33cb171dd06e55.png)

`replacer`に対して関数を渡してあげると、`JSON.stringify()`が実際に対象の値を文字列化させる際の挙動を置き換えることが出来ます。

例えば先程のオブジェクトに下記の`replacer`を適用すると...

```js
function replacer(k, v)
{
    if (typeof v === 'string') {
        return undefined;
    }
    
    return v;
}

console.log(JSON.stringify(object, replacer));
```

こうなります。

```js
{"key3":12345}
```

`replacer`から`undefined`が返却されたキーは削除されます。


# これを利用して

オブジェクト内の値に関数が含まれていた場合はその関数を文字列化することで、どうにか保持してみようと思います。
Javascriptの関数は.toString()を呼ぶことでその関数自体のソースコードを表す文字列を返してくれます。

ということで下記のような関数を作成しました。

```js
function toJson(value)
{
    return JSON.stringify(value, replacer);

    function replacer(k, v) {
        if (typeof v === 'function') {
            return v.toString()
        }

        return v;
    }
}
```

`replacer`の中に入ってきた`value`が関数であれば`.toString()`したものを返しています。
これを先程のオブジェクトに適用すると...

```js
const jsonString = toJson(object);
console.log(jsonString);
```

こうなります。

```js
{"key1":"string","key2":"\\","key3":12345,"key4":"function () { return true; }"}
```

見事、関数を保持することが出来るようになりました。


# しかし

この状態だと当然この文字列を復元した際、`key4`は文字列として復元されます。当然ですね。

```
console.log(JSON.parse(jsonString));
```

![](https://i.gyazo.com/8c91bc38999eed76d9ba058845a1dc83.png)


# それも困る

このままだと折角保護した関数を使うことが出来ません。関数は関数のまま復元したいのです。


# そこで

`JSON.parse()`には`reviver`というものを渡せるようになっています。
これは先程の`replacer`と似ていて、指定することで`JSON.parse()`がJSON文字列を復元させる際の処理を上書くことが出来ます。

![](https://i.gyazo.com/72088957daf72aaaea28e5fc1d6dddd2.png)


# ということで

下記のような`reviver`を定義します。

```js
function reviver(k, v) {
    if (typeof v === 'string' && v.match(/^function/)) {
        return Function.call(this, 'return ' + v)();
    }

    return v;
}
```

文字列である、かつ`function`から始まっていることを条件に関数文字列を実際の関数に戻す処理を挟んでいます。

これを`JSON.parse()`に適用すると...

```js
JSON.parse(jsonString, reviver);
```

![](https://i.gyazo.com/2fdb65401c0fe15b7f0e80dbc1e978c6.png)

無事関数も復元されました。
下記のような関数を定義しておくと便利かと思います。

```js
function fromJson(value)
{
    return JSON.parse(value, reviver);

    function reviver(k, v) {
        if (typeof v === 'string' && v.match(/^function/)) {
            return Function.call(this, 'return ' + v)();
        }

        return v;
    }
}
```

ネストしたオブジェクトでも問題なく対応可能です。

```js
const object = {
    key1: 'string',
    key2: '\\',
    key3: 12345,
    key4: function() { return true; },
    key5: {
        key1: function() { return false; },
        key2: {
            key1: function() { return true; },
            key2: {
                key1: function() { return false; }
            }
        }
    }
};

const jsonString = toJson(object);

console.log(fromJson(jsonString));
```

![](https://i.gyazo.com/a7c60bd3fc8703b62757d10cd6fbd4cb.png)


# まとめ

`replacer`と`reviver`を上手く活用することでFunctionセーフなJSON.stringiryを作成することが出来ました。
複数のRESTなアプリケーションを跨いで設定値の連携を行う場合等、便利かもしれません。