1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

私から見た JSON

Last updated at Posted at 2021-04-27

JSON の特徴

あくまでもテキストデータ(文字列型)

JSON はデータを表現する表記方法です.基本的には文字列型です.

各プログラム言語のデータ型は JSON 形式で書き下す定義を与えることができます
(Serialize,JSON#dump など).

逆に JSON の表記方法で書かれた文字列を解釈して,
それぞれのプログラミング言語が持つデータ型に変換もできます
(Deserialize,JSON#parse など).

JSON(=String) プログラミング言語での表記例
12 int32
"12" String
[1, 2] Array<int32>
{ "a": 12 } HashMap<String, int32>
{ "a": 12, "b": "12" } HashMap<String, int32 | String>

あくまでもデータ記述言語

設定ファイルなどに JSON が使われたりしますが,基本的に JSON はデータ記述言語なので,
コメントなどを記述する必要はありません.

そういった用途には,私は YAML や TOML を使っています.

基礎データの少なさ

これだけ単純な記法にもかかわらず,様々な製品の利用に耐えられているのには驚嘆します.

int32int64 を区別しないのは割り切りがいいと思います.

複合型ではない Datetime などのデータ型も String でまとめたのは見事です.

細部の型はデータを解釈するプログラム言語側に任せ,
少ないデータ種類で柔軟な表現力を維持させた割り切りの良さは素晴らしいと思います.

NULL の扱い

Null の扱いは多くの言語で問題ですが,JSON も例外ではないと思います.

JSON の場合,問題は主に 2 点と考えています.

  • Null を定義しづらい言語もある.定義しない言語もある.
  • 同じ言語でも,ライブラリによって例外を投げたり投げなかったり挙動がバラバラ

基本的に私は Null を使うのを避けています.
(Null の存在を許容するのはインターフェース設計の未成熟と信じています)

それでも,JSON で Null を表現したい場合,その表現方法は 2 種類あります.

  • nullable: ヌルを値として受け入れる. { "key": null } とか明示的に与える.
  • required: ヌルに相当する場合,値を入れない. {} のようにそもそもデータを見せない.

昔は Null を嫌っていたので nullable: false, required: false に統一してましたが,
今は下記のような切り分けに落ち着いています.

nullable required {} { "key": null }
入力 true false OK OK
出力 true true - 全て出力
  • 入力はデバックなどでも使うので,データを入れなくても 良いしNull でも良い.
  • 出力はデータ種類の全容を知りたい.必ず null とし,使わないデータは単に無視する

複合型の貧弱さ

複合型には Array と Object の 2 種類しかありません.

少な過ぎたかもしれません.用途によって,いくつかの分類の切り口があるかと思います.

  • 構造(Object)か,集合(Array)か
  • 順番を持っている(Array)か,いない(Object)か
  • キー(ラベル)を持っている(Object)か,いない(Array)か

特にObject型は,私の知る限り 4 つの概念を持っています

  • 構造体のフィールド(Key)とデータ(Value)

  • 辞書としてのキー(Key)とデータ(Value)

    • キーの投入順番に意味のある場合
    • キーの比較順序に意味のある場合
    • キーの順番に意味がない場合

あえて Rust で表現するなら,次の様になるでしょう.

// 1. 構造体のフィールド(Key)とデータ(Value)
struct KeyValues1{
    key1: value1
    key2: value2
}

// 辞書としてのキー(Key)とデータ(Value)
// 2. キーの投入順番に意味のある場合
type KeyValues2 = Vec<(Key, Value)>;

// 3. キーの比較順序に意味のある場合
type KeyValues3 = BTreeMap<Key, Value>;

// 4. キーに順番がない場合
type KeyValues4 = HashMap<Key, Value>;


// 2,3,4 はイテレータで同じようにアクセスすることができる
for (key, value) in key_values.iter() {
    // とりあえず,全ての (key, value) が1順してやってくる
    //
    // 2 なら JSON で宣言したキーの順番通りに
    // 3 なら JSON での宣言した順番とは関係なく,キーの比較された順序で
    // 4 なら JSON での宣言した順番とは関係なく,ライブラリ内の何かの規則で
}

私はほとんどの場合,データ構造を持つ型は Vec, BTreeMap, HashMap で補います.
大抵のキーバリューは上の 3 つの分類ができれば十分です.
(性能が問われると,特別なデータ構造を設計したりしますが,極力最適化しない人です)

さて,これらの 4 つの解釈は JSON の範囲を超えています.

私が見てきた多くの JSON ライブラリは,各Object型に対して 4 つの分類を指定できるように作られてはいないので,
自分で読み取り機能を実装するか,読み取った後で並べ替えをすることになります.

特に,複数のシステムを経由したデータでキーの宣言した順番を維持したい場合,保証は困難です.
JSONの Object 自体はキーの宣言の順番を保証していないので実装依存です.
経由する全てのシステムのJSONパーサがキーの順番を保証することを,
使用するライブラリの将来のアップデートまで考慮して保証しなければなりません.

そこで,私は以下のような厳しい基準を採用していました.

  • 辞書としてのObject は集合と解釈し,キーに順番はない( = HashMap or Struct )
  • 順番に意味がある場合は Array<(Key, Value)> で全て表し, Object を用いない

キーの順番が重要なら,JSON の生成者が Array の投入順番を考慮して作成すべしと考えてました.

この考えを軟化させたのには,2 つの理由があります.

Git の競合を回避する

パッケージ管理の設定ファイルなどで顕著ですが,新しく追加するものを常に末尾におく場合,
複数人での作業では Git の競合が頻発します.

これを避けるアプローチの一つとして,パッケージ名でソートすることが考えられます.
異なるパッケージは異なる場所に分散して挿入される様にすれば良いという考えです.
このアプローチは,根本的に Git の競合を根絶することはできないでしょう
(同名パッケージのバージョン違い,類似名のパッケージインストール etc...)

しかし,常に最後に追加するアプローチに比べ,名前順による管理は競合の被弾率を低下させます.

エディタの進化

VSCode で仕事をしていたときのことです.
エディタの breadcrumb にキーの名前が出てくることに気づきました.
自分が今どのスコープを作業しているかが容易に判断できます.

Object.png

これが,配列だと連番なので何のデータを作業しているのかさっぱりわかりません.

Array.png

う〜ん,欲しい!!

そこで,私は今や 4 種類全てを使い分ける様になったとさ.

その他

  • 外部とのやり取りで JSON を使うのは生産性が高い
  • DB 内で使うのも有り(自分たちがコントロールしていないデータを保存する場合は)
  • Rust のような高級プログラミング言語内で引き回すのは反対(構造体にマップしろ!!)

まとめ

JSON は素晴らしいフォーマットです.
しかし,NULL や Object の使い分けは少し悩むかもしれないという話でした。

1
1
0

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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?