LoginSignup
51
44

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-10-06

はじめに

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

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

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

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

こうなります。

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

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

それでは困る

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

そこで

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

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

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

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

    return v;
}

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

こうなります。

{"key3":12345}

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

これを利用して

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

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

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

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

        return v;
    }
}

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

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

こうなります。

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

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

しかし

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

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

それも困る

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

そこで

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

ということで

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

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

    return v;
}

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

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

JSON.parse(jsonString, reviver);

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

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;
    }
}

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

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));

まとめ

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

51
44
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
51
44