できない.dev

JSONをパースするには

JSON 文字列やファイルを各言語のデータ構造へ変換する基本形を示す。
パース失敗の扱いと、型が保証されない点への向き合い方までを扱う。

公開: 更新:

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

Python

import json
 
with open("config.json", encoding="utf-8") as f:
    data = json.load(f)
 
print(data["name"])

ファイルから読むときは json.load、既に文字列になっているときは json.loads を使う。
encoding を明示するのは、Python 3.14 までは省略時の既定値がロケール依存(日本語版 Windows は cp932)で、日本語が文字化けする事故を防ぐためである。
3.15 から既定は UTF-8 になったが、版によって読み方が変わらないよう明示する。

できないときPython「json.decoder.JSONDecodeError: Expecting value」の原因と直し方

JavaScript

import { readFile } from "node:fs/promises";
 
const raw = await readFile("config.json", "utf-8");
const data = JSON.parse(raw);
 
console.log(data.name);

JSON.parse は文字列だけを受け取るため、ファイルは先にテキストとして読み込む必要がある。
トップレベル await を使う場合はファイルが ESM として扱われている必要がある。

TypeScript

type Config = { name: string; port: number };
 
const raw = '{"name":"api","port":8080}';
const data = JSON.parse(raw) as Config;
 
console.log(data.port);

JSON.parse の戻り値は any なので、型注釈を付けても中身が検証されるわけではない。
外部から来る JSON を扱うなら as で断言せず、zod などで実行時に検証する方が安全である。

Go

package main
 
import (
	"encoding/json"
	"fmt"
)
 
type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}
 
func main() {
	data := []byte(`{"name":"taro","age":30}`)
	var u User
	if err := json.Unmarshal(data, &u); err != nil {
		panic(err)
	}
	fmt.Println(u.Name, u.Age)
}

json.Unmarshal には構造体のポインタを渡す。
値のまま渡すと「json: Unmarshal(non-pointer main.User)」のエラーになる。
小文字始まりの非公開フィールドは json タグを付けても無視されるため、フィールドは大文字で始め、json タグで JSON 側のキー名と対応付ける。

Rust

use serde::Deserialize;
 
#[derive(Deserialize)]
struct User {
    name: String,
    age: u32,
}
 
fn main() -> Result<(), serde_json::Error> {
    let data = r#"{"name":"taro","age":30}"#;
    let u: User = serde_json::from_str(data)?;
    println!("{} {}", u.name, u.age);
    Ok(())
}

Rust の標準ライブラリに JSON パーサは無いため、serde と serde_json をクレートとして追加する。
derive(Deserialize) を使うには serde の derive フィーチャーが要るので、cargo add serde --features derive で追加する。
構造体にあるフィールドが JSON に無いと、コンパイル時ではなく実行時に missing field の Err になる。
JSON 側の余分なキーは既定で無視される。

Java

import com.fasterxml.jackson.databind.ObjectMapper;
 
public class Main {
    public static void main(String[] args) throws Exception {
        String json = "{\"name\":\"taro\",\"age\":30}";
        User u = new ObjectMapper().readValue(json, User.class);
        System.out.println(u.name() + " " + u.age());
    }
}
 
// java Main.java で直接実行すると先頭のクラスの main が呼ばれるため、record は後ろに置く
record User(String name, int age) {}

JDK の標準ライブラリには JSON パーサが含まれないため、Jackson や Gson を依存に追加する必要がある。
Jackson の ObjectMapper は readValue に渡した型へマッピングし、record にも対応する。
JSON 側に未知のキーがあると既定で UnrecognizedPropertyException になるので、必要なら FAIL_ON_UNKNOWN_PROPERTIES を無効にする。
逆にキーが欠けていても例外にはならず、int のフィールドは 0 のまま入る。

C#

using System.Text.Json;
 
var json = """{"name":"taro","age":30}""";
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var user = JsonSerializer.Deserialize<User>(json, options);
Console.WriteLine($"{user!.Name} {user.Age}");
 
record User(string Name, int Age);

System.Text.Json は既定でプロパティ名の大文字小文字を区別する。
camelCase の JSON を受け取るときに PropertyNameCaseInsensitive を有効にしないと、例外にもならず Name は null、Age は 0 のまま返る。

つまずき

パースに失敗するときは、まず入力が本当に JSON かを疑う。
HTTP レスポンスをそのまま渡した結果、エラーページの HTML をパースしようとしている例が多い。
末尾カンマやシングルクォートは JSON では許されない点も、設定ファイルを手書きしたときの定番の原因である。

型は信用しない

静的型付けの言語であっても、パース結果の型は宣言しただけでは保証されない。
外部 API や設定ファイルのように自分が生成していないデータは、キーの有無や値の型を実行時に検証してから使うのが安全である。

このコードで動いた?