JSON.stringifyって便利ですよね。
- 簡単に成形できていい感じです。
json.js
var obj = {
a: 1,
b: "Moji",
c: [1, 2, 3, 4],
d: {
"A": 1,
"B": 2
}
};
console.log(JSON.stringify(obj));
console.log(JSON.stringify(obj,undefined,1));
結果
{"a":1,"b":"Moji","c":[1,2,3,4],"d":{"A":1,"B":2}}
{
"a": 1,
"b": "Moji",
"c": [
1,
2,
3,
4
],
"d": {
"A": 1,
"B": 2
}
}
でも、(obj,undefined,1)ってなんだろ?
- 何の気なしにundefinedって指定してたんですが、これってホントはなんなんでしょ?
仕様を調べてみます。
- mozillaのサイトを見てみると...
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
構文
JSON.stringify(value[, replacer[, space]])
- replacerってなんでしょ?spaceは字下げっぽいですね。
引数
-
value
- JSON 文字列に変換する値。
-
replacer
Optional- もし関数である場合、文字列化の間に出会った値とプロパティを変換します。もし配列である場合は、最終的な文字列のオブジェクトに含まれるプロパティの集合を指定します。
-
space
Optional- 結果の文字列を整形して出力します。
spaceから試します。
- 説明がありました。
space 引数は最終的な文字列でのスペーシングを調整するために用いることができます。もし数値であれば、文字列化での連続するレベルはそれぞれその量(10 まで)の空白文字でインデントされます。もし文字列であれば、連続するレベルはこの文字列(またはその最初の 10 文字)でインデントされます。
- へー、数字でもいいし、文字でもいいんだ!
インデント空白4文字
console.log(JSON.stringify(obj,undefined,4));
{
"a": 1,
"b": "Moji",
"c": [
1,
2,
3,
4
],
"d": {
"A": 1,
"B": 2
}
}
インデント空白10文字
console.log(JSON.stringify(obj,undefined,10));
{
"a": 1,
"b": "Moji",
"c": [
1,
2,
3,
4
],
"d": {
"A": 1,
"B": 2
}
}
インデント空白100文字
- 100文字にしても10文字と同じです。
console.log(JSON.stringify(obj,undefined,100));
{
"a": 1,
"b": "Moji",
"c": [
1,
2,
3,
4
],
"d": {
"A": 1,
"B": 2
}
}
インデントにタブ文字
console.log(JSON.stringify(obj,undefined,"\t"));
{
"a": 1,
"b": "Moji",
"c": [
1,
2,
3,
4
],
"d": {
"A": 1,
"B": 2
}
}
インデントに記号
- JSONフォーマットじゃなくなりますがこんなのでもできました!
console.log(JSON.stringify(obj,undefined,"🐻"));
{
🐻"a": 1,
🐻"b": "Moji",
🐻"c": [
🐻🐻1,
🐻🐻2,
🐻🐻3,
🐻🐻4
🐻],
🐻"d": {
🐻🐻"A": 1,
🐻🐻"B": 2
🐻}
}
replacerとは??
- replacerには関数か配列が使えてjsonの置き換え(フィルター?)ができるようです。
replacer関数を使ってみます。
- replacerは、key,valueの値をとって、stringであれば無効(?)にしてそれ以外はvalueを返すという意味です。
replacer関数
function replacer(key, value) {
if (typeof value === 'string') {
return undefined;
}
return value;
}
var obj = {
a: 1,
b: "Moji",
c: [1, 2, 3, 4],
d: {
"A": 1,
"B": 2
}
};
console.log(JSON.stringify(obj, replacer, "\t"));
結果
{
"a": 1,
"c": [
1,
2,
3,
4
],
"d": {
"A": 1,
"B": 2
}
}
- 文字が無くなりましたね!
replacerの配列?
- 配列ってなんでしょ?
replacer配列
var replacer = ['a','d','A'];
var obj = {
a: 1,
b: "Moji",
c: [1, 2, 3, 4],
d: {
"A": 1,
"B": 2
}
};
console.log(JSON.stringify(obj, replacer, "\t"));
結果
{
"a": 1,
"d": {
"A": 1
}
}
- なるほど!指定のkeyだけフィルタされるんですね!
replacer関数 応用編
- 関数なのでいろいろいじれちゃいますね。
replacer
function replacer(key, value) {
var ret = value;
var now = String(new Date());
if (key === 'A') { // key:Aの値は現在時刻に
ret = now;
} else if (typeof value === 'number') { // 数値は文字列に変換
ret = String(value);
}
return ret;
}
こんな結果になりました。
{
"a": "1",
"b": "Moji",
"c": [
"1",
"2",
"3",
"4"
],
"d": {
"A": "Wed Feb 17 2016 23:12:21 GMT+0900 (JST)",
"B": "2"
}
}
- ちゃんと数値が"文字列"になり、"A"の値がDateになりました。
replacer関数は全要素を舐める
- この挙動からわかるようにreplacerは全要素を舐めてくれます。
全要素
function replacer(key, value) {
console.log("key:[" + key + "]\t type:[" + (typeof value) + "]\tvalue:[" + value + "]");
return value;
}
JSON.stringify(obj, replacer, "\t");
ツリーでたどっていくみたい
key:[] type:[object] value:[[object Object]]
key:[a] type:[number] value:[1]
key:[b] type:[string] value:[Moji]
key:[c] type:[object] value:[1,2,3,4]
key:[0] type:[number] value:[1]
key:[1] type:[number] value:[2]
key:[2] type:[number] value:[3]
key:[3] type:[number] value:[4]
key:[d] type:[object] value:[[object Object]]
key:[A] type:[number] value:[1]
key:[B] type:[number] value:[2]
まとめ
- replacerって便利!!
- 表示だけならいいけど、これでフィルタして再利用するのは、なんかちょっと危険な予感...
追記(2026年)
- 2026年6月に策定された ECMAScript 2026 で、
JSONまわりに新機能が追加されました。この記事のreplacerと対になる話なので追記しておきます。
JSON.rawJSON() / JSON.isRawJSON() が登場
-
JSON.stringifyは今までBigIntをそのまま渡すとエラーになっていました(丸めが起きて値が壊れるため)。
const myBigInt = 12345678901234567890n;
JSON.stringify({ myBigInt }); // ⚠️ TypeError: Do not know how to serialize a BigInt
-
JSON.rawJSON()を使うと、任意のJSONテキストを"生の値"としてそのまま埋め込めるようになりました。replacer関数と組み合わせると、大きな数値も精度を落とさず出力できます。
const payload = {
id: 90071992547409931234567890n,
count: 3n,
};
const json = JSON.stringify(payload, (key, value) => {
if (typeof value === "bigint") {
return JSON.rawJSON(value.toString());
}
return value;
});
console.log(json);
// {"id":90071992547409931234567890,"count":3}
-
JSON.isRawJSON(value)で、その値がrawJSONで作られたものかどうかを判定できます。生成されたrawJSONオブジェクトはfreezeされた特殊なオブジェクトで、JSON.stringifyが見つけると中身のJSONテキストをそのまま展開してくれます。
JSON.parseのreviverにも第3引数が追加
- この記事の主題だったreplacerの逆側、
JSON.parseのreviverにも変更が入りました。今までは(key, value)の2引数でしたが、(key, value, context)の3引数目として、元のJSONソーステキストにアクセスできるcontext.sourceが渡されるようになりました。
const jsonText = `{"big": 9007199254740993}`;
const reviver = (key, value, { source }) => {
if (typeof value === "number" && source && !Number.isSafeInteger(value)) {
return BigInt(source);
}
return value;
};
const parsed = JSON.parse(jsonText, reviver);
console.log(parsed.big); // 9007199254740993n
-
Numberで精度が落ちる大きな整数を、パース時点でソーステキストから直接BigInt化できるようになったのがポイントです。rawJSONと組み合わせれば、大きな数値をロスレスにstringify→parseの往復ができるようになります。 -
replacer/reviverは「値を書き換える」ためのフックでしたが、これらの追加でついに「JSONの精度そのもの」まで手が届くようになった、という感じですね。