Skip to content

Progress Log: JSON形式についての調査と整理

Task Description

JSON (JavaScript Object Notation) 形式について、以下の観点から調査・整理をしました。

  • JSONについての基本情報
  • JSON形式のサンプル
  • JSON形式の利用が適している分野
  • JSON形式の派生形式
  • JSON形式を扱うライブラリ(C/C++とPython)

Outcome

JSON形式に関する包括的なまとめを作成しました。

JSONについて

JSON (JavaScript Object Notation) は、テキストベースの軽量なデータ交換形式です。

主な特徴:

  • シンプルで読みやすい: テキストベースで人間が読みやすい構文
  • 言語中立: JavaScriptの構文に基づくが、すべてのプログラミング言語で解析可能
  • 軽量: XMLと比較してファイルサイズが小さい
  • 構造化: オブジェクト(連想配列)と配列をサポート
  • 標準化: RFC7158で標準化されている

基本データ型:

  • オブジェクト: { "key": value, ... } - キー値ペアの集合
  • 配列: [ value, value, ... ] - 値の順序付きリスト
  • 文字列: "text" - ダブルクォートで囲まれた文字列
  • 数値: 123, 45.67, 1.23e-4 - 整数と小数、科学記法
  • 真偽値: true, false - 論理値
  • null: null - 空の値

JSON形式のサンプル

シンプルなオブジェクト:

{
  "name": "OSECHI",
  "type": "cosmic-ray-detector",
  "version": "1.6.1",
  "enabled": true
}

ネストされたオブジェクト:

{
  "device": {
    "name": "OSECHI",
    "board": "ESP32-WROOM-32E",
    "specs": {
      "cpu_cores": 2,
      "clock_mhz": 240,
      "ram_kb": 320,
      "flash_mb": 4
    }
  }
}

配列を含む例:

{
  "detections": [
    {
      "timestamp_us": 1234567890,
      "signal1": 95,
      "signal2": 87,
      "signal3": 92,
      "temperature": 25.35,
      "pressure": 1013.25,
      "humidity": 45.67
    },
    {
      "timestamp_us": 1234568901,
      "signal1": 100,
      "signal2": 78,
      "signal3": 85,
      "temperature": 25.40,
      "pressure": 1013.28,
      "humidity": 45.72
    }
  ]
}

センサー構成データ:

{
  "configuration": {
    "detector": {
      "poll_count": 100,
      "channels": [
        {"id": 1, "pin": 12, "threshold": 50},
        {"id": 2, "pin": 19, "threshold": 50},
        {"id": 3, "pin": 27, "threshold": 50}
      ]
    },
    "sensors": {
      "bme280": {"address": "0x76", "enabled": true},
      "adc": {"pin": 32, "resolution": 12}
    }
  }
}

JSON形式の利用が適している分野

  1. API通信: REST APIのリクエスト/レスポンス形式として最も広く使用
  2. 設定ファイル: アプリケーション設定の保存(例: package.json, tsconfig.json
  3. ロギング: 構造化ログの標準フォーマット
  4. データストレージ: NoSQLデータベース(MongoDB, Firebaseなど)
  5. IoT/組込み: センサーデータの送受信フォーマット
  6. Webデータ交換: ブラウザとサーバー間のデータ交換
  7. メタデータ: プロジェクトメタデータ、パッケージ情報の記述
  8. キャッシュ: メモリキャッシュやキャッシュファイルの格納

JSON形式の派生

JSONL (JSON Lines)

  • 1行に1つのJSON オブジェクトを記述
  • ストリーミング処理やログファイルに適している
  • 例:
{"id": 1, "name": "Event1", "value": 95}
{"id": 2, "name": "Event2", "value": 87}

NDJSON (Newline Delimited JSON)

  • JSONLと同じ仕様(別名称)
  • データストリーム処理に最適

JSON5

  • JSONの拡張仕様
  • コメント、末尾のカンマ、シングルクォート対応
  • 開発用設定ファイルで使用

JSONP (JSON with Padding)

  • クロスオリジンでのデータ取得(古い方式、現在はCORSが主流)
  • コールバック関数でJSONを囲む
  • 例: callback({"key": "value"})

Canonical JSON

  • JSON要素の順序を固定化したバージョン
  • 暗号署名や検証が必要な場合に使用

JSON形式を扱うライブラリ

Python

標準ライブラリ:

  • json - Python標準モジュール
import json

# JSON文字列をPythonオブジェクトに変換
data = json.loads('{"name": "OSECHI", "version": "1.6.1"}')

# PythonオブジェクトをJSON文字列に変換
json_str = json.dumps(data, indent=2)

# ファイルから読み込み
with open('config.json', 'r') as f:
    config = json.load(f)

# ファイルに書き込み
with open('output.json', 'w') as f:
    json.dump(data, f, indent=2)

高機能ライブラリ:

  • orjson - 高速なJSON処理(C実装)
import orjson
data = orjson.loads(json_bytes)
json_bytes = orjson.dumps(data)
  • ujson - マイクロPythonのJSON実装
  • マイクロコントローラーでのJSON処理に最適

  • pydantic - JSON スキーマ検証

from pydantic import BaseModel

class Detection(BaseModel):
    signal1: int
    signal2: int
    temperature: float

C/C++

軽量ライブラリ:

  • cJSON - シンプルで軽量(C実装)
#include "cjson/cJSON.h"

cJSON *root = cJSON_Parse(json_string);
cJSON *item = cJSON_GetObjectItem(root, "name");
cJSON_Delete(root);
  • jansson - C言語用の高品質JSONライブラリ
#include <jansson.h>

json_t *root = json_loads(json_string, 0, &error);
json_object_set_new(root, "key", json_string("value"));

高機能ライブラリ:

  • RapidJSON - 高速なC++JSONライブラリ
#include "rapidjson/document.h"

rapidjson::Document doc;
doc.Parse(json_string);
const char* name = doc["name"].GetString();
  • nlohmann/json - モダンC++ JSON ライブラリ
#include <nlohmann/json.hpp>
using json = nlohmann::json;

json data = json::parse(json_string);
std::cout << data["name"] << std::endl;
  • Boost.JSON - Boost C++ライブラリのJSON実装
  • 高度な機能と最適化

組込み/マイコン向け:

  • ArduinoJson - Arduino/ESP32向けの軽量JSONライブラリ
#include <ArduinoJson.h>

StaticJsonDocument<256> doc;
deserializeJson(doc, json_string);

int value = doc["value"];
doc["name"] = "OSECHI";

String output;
serializeJson(doc, output);

JSONL形式をKurikintonsで使う場合

JSONL(JSON Lines)形式は、Kurikintonsの検出データロギングにとくに適しています。

利点:

  1. ストリーミング処理に最適: 各検出イベントごとに1行のJSONオブジェクトを記録
  2. ESP32がリアルタイムでファイルに追記可能
  3. 完全なファイルパースが不要(パーシャルデータに強い)
  4. メモリ使用量が最小限(1行ずつ処理可能)

  5. 検出イベント履歴に最適: 時系列データの記録として自然

  6. 新しい検出データが行追加されていく形式
  7. 古いデータと新しいデータの混在が容易

  8. データ解析が簡単: Python での処理が直感的

  9. for line in file: data = json.loads(line) で読める
  10. grep/awk などのテキストツールと相性良好

  11. リアルタイム監視に対応: ログファイル監視ツールと連携可能

  12. tail -f でリアルタイム追従可能
  13. ストリーミング解析に向いている

Kurikintons での想定用途:

ファイル構成例(detection_log.jsonl):

{"timestamp_us": 1000000, "signal1": 95, "signal2": 87, "signal3": 92, "adc": 2048, "temp": 25.35, "pressure": 1013.25, "humidity": 45.67}
{"timestamp_us": 2500000, "signal1": 100, "signal2": 78, "signal3": 85, "adc": 1950, "temp": 25.40, "pressure": 1013.28, "humidity": 45.72}
{"timestamp_us": 4100000, "signal1": 88, "signal2": 92, "signal3": 79, "adc": 2100, "temp": 25.38, "pressure": 1013.26, "humidity": 45.70}

ESP32 側の実装想定:

// ArduinoJson を使用して JSONL 形式で記録
#include <ArduinoJson.h>

void log_detection_to_jsonl(const cosmic_detection_t& detection) {
  StaticJsonDocument<256> doc;
  doc["timestamp_us"] = micros();
  doc["signal1"] = detection.signal1;
  doc["signal2"] = detection.signal2;
  doc["signal3"] = detection.signal3;
  doc["adc"] = adc_value;
  doc["temp"] = temperature;
  doc["pressure"] = pressure;
  doc["humidity"] = humidity;

  // シリアル出力または SDカード に追記
  serializeJson(doc, Serial);
  Serial.println();  // 改行で1行を終了
}

Python 側での解析例:

import json
from datetime import datetime

def analyze_detection_log(filename):
    """JSONL ログファイルを解析"""
    detections = []

    with open(filename, 'r') as f:
        for line in f:
            if line.strip():
                data = json.loads(line)
                detections.append(data)

    # 検出数
    print(f"Total detections: {len(detections)}")

    # 平均信号値
    avg_signal1 = sum(d['signal1'] for d in detections) / len(detections)
    print(f"Average signal1: {avg_signal1:.2f}")

    # イベント間隔分析
    for i in range(1, len(detections)):
        interval_us = detections[i]['timestamp_us'] - detections[i-1]['timestamp_us']
        interval_ms = interval_us / 1000
        print(f"Event {i}: {interval_ms:.2f} ms interval")

    return detections

# リアルタイムログ監視
def monitor_log_file(filename):
    """新しい検出をリアルタイム監視"""
    import time

    last_pos = 0
    while True:
        with open(filename, 'r') as f:
            f.seek(last_pos)
            for line in f:
                if line.strip():
                    data = json.loads(line)
                    print(f"Detected: {data['signal1']}, {data['signal2']}, {data['signal3']}")
            last_pos = f.tell()

        time.sleep(1)

利点の総括:

  • ストリーミング: JSONL形式は追記が簡単、通常のJSON配列は再パースが必要
  • メモリ効率: JSONL形式は1行ずつ処理、通常のJSON配列は全体をパース
  • ファイルサイズ: ほぼ同等
  • テキストツール連携: JSONL形式はgrep/awk可能、通常のJSON配列は複雑
  • 人間可読性: JSONL形式は行ごとに読める、通常のJSON配列は構造が見やすい

データ量と送信時間の比較

現在のフォーマット(スペース区切り):

signal1 signal2 signal3 adcValue temperature pressure humidity [uptime_ms] [duration_us]
100 85 92 2048 25.35 1013.25 45.67 123456 1234567890

スペース区切り形式(現在)

サンプルデータ:

100 85 92 2048 25.35 1013.25 45.67 123456 1234567890

データ量計算:

  • 数値部: 100 85 92 2048 25.35 1013.25 45.67 123456 1234567890
  • スペース: 8個
  • 改行: 1文字(\n)
  • 合計: 59バイト

JSONL形式

サンプルデータ:

{"signal1":100,"signal2":85,"signal3":92,"adc":2048,"temp":25.35,"pressure":1013.25,"humidity":45.67,"uptime_ms":123456,"duration_us":1234567890}

データ量計算:

  • キー: signal1, signal2, signal3, adc, temp, pressure, humidity, uptime_ms, duration_us
  • 値: 数値部
  • JSON構造: {, }, :, , など
  • 改行: 1文字(\n)
  • 合計: 151バイト

形式別比較

1レコード当たりのデータ量:

  • スペース区切り形式: 59バイト
  • JSONL形式: 151バイト
  • オーバーヘッド(スペース区切り): 8バイト(スペース+改行)
  • オーバーヘッド(JSONL形式): 92バイト(JSON構造+キー)
  • オーバーヘッド率(スペース区切り): 13.6%
  • オーバーヘッド率(JSONL形式): 61.0%

大規模データでの比較(検出頻度1Hz = 1秒に1回):

  • 1時間: スペース区切り59KB、JSONL形式151KB
  • 1日: スペース区切り1.4MB、JSONL形式3.6MB
  • 1ヶ月: スペース区切り42MB、JSONL形式108MB

送信時間の計算

ボーレート: 115200 bps(9600バイト/秒)

単一レコード送信時間:

  • スペース区切り形式(59バイト): 6.1ms
  • JSONL形式(151バイト): 15.7ms
  • 時間差: +9.6ms(16.3%増加)

実運用での送信スループット:

  • 低頻度検出の場合(検出頻度: 1回/分):
  • スペース区切り形式: 約118バイト/分
  • JSONL形式: 約302バイト/分
  • 帯域幅への影響: ほぼ無視できる(115.2kbpsに対して数百バイト/分)

  • 高頻度検出の場合(検出頻度: 1回/秒):

  • スペース区切り形式: 約3.5KB/分
  • JSONL形式: 約9.1KB/分
  • 帯域幅への影響: 依然として軽微(115.2kbpsに対して9KB/分)

結論

  • データ量増加: 約2.6倍増加するが、ボーレート115200では実運用への影響はほぼ無視できる
  • 利点: ストリーミング処理、解析の容易さがデメリット(データ量増)を大きく上回る
  • 推奨: JSONLへの移行は十分に実行可能で、設計上のメリットが大きい

Learnings

  1. JSONは通信フォーマットとして最適: テキストベース、軽量、言語中立で多くのシステムに対応
  2. 派生形式が用途に応じて存在: JSONLはログストリーミング、JSON5は開発用設定に適している
  3. C/C++では複数の選択肢がある: 軽量(cJSON)から高機能(RapidJSON, nlohmann/json)まで用途に応じて選択可能
  4. ESP32/Arduino向けにはArduinoJsonが標準: マイコンのメモリ制約に最適化されている
  5. Pythonはjsonモジュールで十分: 標準ライブラリで大抵のケースに対応可能
  6. Kurikintons には JSONL 形式が最適: ストリーミング検出データの記録・解析に理想的

Next Steps

  1. KurikintonsでのJSONL実装検討:
  2. ArduinoJsonを使用したJSONL出力機能の設計
  3. マイコン側でのシリアル出力をJSONL形式に統一
  4. SDカードへのJSONLファイル保存機能(オプション)

  5. Python解析スクリプト開発:

  6. JSONLログファイルの自動解析ツール
  7. リアルタイムログ監視スクリプト
  8. グラフ描画による検出パターン分析

  9. テスト/ベンチマーク:

  10. JSONL形式でのシリアル出力パフォーマンステスト
  11. 大規模検出ログの解析性能確認
  12. メモリ使用量の実測定