できない.dev

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

オブジェクトや構造体を JSON 文字列へ変換する基本形を各言語で示す。
整形の有無と、日本語などの非 ASCII 文字を既定でエスケープするかどうかの違いまでを扱う。

公開: 更新:

ブラウザで試すJSON 整形・エラー箇所チェック

選んだ言語は、ほかのサンプルコードを開いたときも最初に表示する。

Python

import json
 
user = {"name": "山田", "tags": ["api", "cli"], "active": True}
text = json.dumps(user, ensure_ascii=False, indent=2)
 
print(text)

json.dumps は既定で非 ASCII 文字を \uXXXX へエスケープするため、日本語をそのまま出したいときは ensure_ascii=False を指定する。
indent を渡すと人が読める整形出力になり、省略すれば区切りの詰まった 1 行になる。

JavaScript

const user = { name: "山田", tags: ["api", "cli"], active: true };
const text = JSON.stringify(user, null, 2);
 
console.log(text);

第 2 引数は出力するキーを絞る replacer、第 3 引数がインデント幅である。
整形が不要なら JSON.stringify(user) だけでよい。
undefined や関数を値に持つプロパティは黙って出力から落ちる点に注意する。

できないときReact「Objects are not valid as a React child」の原因と直し方

TypeScript

type User = { name: string; tags: string[]; active: boolean };
 
const user: User = { name: "山田", tags: ["api", "cli"], active: true };
const text = JSON.stringify(user, null, 2);
 
console.log(text);

型は「どんな JSON になるか」を保証しない。
JSON.stringify の宣言上の戻り値は string だが、undefined を渡した場合の実際の戻り値は undefined なので、任意の値を受け取る関数では戻り値を string と決めつけない方がよい。

Go

package main
 
import (
	"encoding/json"
	"fmt"
)
 
type User struct {
	Name   string   `json:"name"`
	Tags   []string `json:"tags"`
	Active bool     `json:"active"`
}
 
func main() {
	u := User{Name: "山田", Tags: []string{"api", "cli"}, Active: true}
	b, err := json.MarshalIndent(u, "", "  ")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

json.Marshal は 1 行、json.MarshalIndent は整形した []byte を返す。
エクスポートされていない小文字始まりのフィールドは出力されないため、JSON 側のキー名は json タグで指定する。
既定では < > & が HTML 用にエスケープされ、<b>&</b> は \u003cb\u003e\u0026\u003c/b\u003e のように出力される。
そのまま出したいときは json.Encoder の SetEscapeHTML(false) を使う。

Rust

use serde::Serialize;
 
#[derive(Serialize)]
struct User {
    name: String,
    tags: Vec<String>,
    active: bool,
}
 
fn main() -> Result<(), serde_json::Error> {
    let u = User {
        name: "山田".to_string(),
        tags: vec!["api".to_string(), "cli".to_string()],
        active: true,
    };
    println!("{}", serde_json::to_string_pretty(&u)?);
    Ok(())
}

標準ライブラリに JSON は含まれないため、serde と serde_json をクレートとして追加する。
to_string が 1 行、to_string_pretty が整形版で、どちらも Result を返す。
derive(Serialize) を付けていない型を渡すと、the trait bound … is not satisfied(E0277)でコンパイルが止まる。

Java

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
 
record User(String name, List<String> tags, boolean active) {}
 
public class Main {
    public static void main(String[] args) throws Exception {
        var user = new User("山田", List.of("api", "cli"), true);
        var mapper = new ObjectMapper();
        System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user));
    }
}

JDK の標準ライブラリに JSON は含まれないため、Jackson(jackson-databind)や Gson を依存に追加する。
writeValueAsString が 1 行の JSON を返し、writerWithDefaultPrettyPrinter() を挟むと整形される。
整形の書式は "name" : "山田" のようにコロンの前にも空白が入り、短い配列は [ "api", "cli" ] と 1 行に収まる。
record はコンポーネント名がそのままキーになる。

C#

using System.Text.Json;
using System.Text.Encodings.Web;
 
var user = new User("山田", new[] { "api", "cli" }, true);
var options = new JsonSerializerOptions
{
    WriteIndented = true,
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
Console.WriteLine(JsonSerializer.Serialize(user, options));
 
record User(string Name, string[] Tags, bool Active);

System.Text.Json の既定のエンコーダは非 ASCII 文字を \uXXXX へ逃がし、「山田」は \u5C71\u7530 になる。
日本語をそのまま出したいときは UnsafeRelaxedJsonEscaping を指定する。
WriteIndented を true にすると整形出力になる。
キー名は既定でプロパティ名のまま Name Tags Active で出力され、PropertyNamingPolicy = JsonNamingPolicy.CamelCase を指定すると name tags active になる。

つまずき

同じデータでも、非 ASCII 文字を \uXXXX へ逃がすかどうかは言語ごとに既定が違う。
Python の json.dumps と .NET の JsonSerializer は既定でエスケープし、JavaScript の JSON.stringify はエスケープしない。
どちらも JSON としては正しいため、スナップショットテストや差分比較で食い違ったときは、まずこの既定値の違いを疑うとよい。

循環参照は文字列化できない

自分自身を含むオブジェクトは JSON にできず、実行時エラーになる。
Node.js では TypeError: Converting circular structure to JSON、Python では ValueError: Circular reference detected が送出される(いずれも本記事の作成時に手元で発生を確認した)。
親子を相互参照させたデータ構造をそのまま渡したときに起きるので、出力用の形へ詰め替えてから文字列化する。