JSON と JSON5 の違い

JSON と JSON5 は見た目は似ていますが、「どれくらい人間に優しいか」「どれくらい厳格か」で大きく違います

 

🧩 JSONとは

JavaScript Object Notation の略で、
データ交換フォーマットとして最も一般的なものです。

 

✅ 特徴

・厳格でシンプル
・仕様が固定されていて機械処理に最適
・ほとんどの言語・ツールが標準対応

 

📋 制約


{
  "name": "Mao",
  "age": 25,
  "languages": ["Swift", "Kotlin"]
}

・文字列は必ず ダブルクォート
・コメント不可
・末尾カンマ禁止
・キーもクォート必須
・数値表現 は10進数のみ

 

🌈 JSON5とは

JSON for Humans(人間に優しいJSON)で
JSONの上位互換として提案されたフォーマットです。

JavaScriptライクな書き方を許します。

 

✅ 特徴

・より柔軟で可読性が高い
・コメントや末尾カンマをサポート
・キーや文字列のクォート省略OK
・16進数や+∞などの数値表現が可能

 

📋 例


{
  // コメントOK
  name: 'Mao',
  age: 25,
  languages: ['Swift', 'Kotlin',], // 末尾カンマOK
  version: 0x1f, // 16進数OK
}

 

🤔 JSON と JSON5 の比較

JSON は機械向けの厳格フォーマット、JSON5 は「人間向け」に拡張されたフォーマットです。下の表はブログでそのまま使える比較表です。

項目 JSON JSON5
コメント 不可(コメントなし) 可(`//`、`/* */` が使える)
末尾カンマ 不可
キーのクォート 必須(ダブルクォート) 省略可(識別子としてのキーを許可)
文字列のクォート ダブルクォートのみ(`"`) シングルクォートまたはダブルクォート可(`'` / `"`)
数値表現 10進数のみ 10進数、16進数、`NaN`、`Infinity` などを許可
対応ツール 非常に多い(ほぼ標準) ライブラリが必要(サポートは限定的)
用途 機械間通信(API等)に最適 開発者向け設定ファイルや手で編集するデータに適する

 

🧑🏻‍💻 まとめ

- JSON:厳格で機械向け。安全・標準的。
- JSON5:柔軟で人間向け。コメントや末尾カンマOK。


Jetpack Compose 1.7+ でクリップボードコピーをどう書く?

 

🧑🏻‍💻 LocalClipboard と suspend 関数の組み合わせ

Compose 1.7 以降では、従来の ClipboardManager が非推奨になり、代わりに LocalClipboard + 非同期コピー が公式に推奨されています。

以下はシンプルなサンプルです。rememberCoroutineScope を使い、クリックイベントで非同期コピーを行っています。


val clipboard = LocalClipboard.current
val scope = rememberCoroutineScope()

Box(modifier = Modifier.clickable {
    scope.launch {
        val clipData = ClipData.newPlainText(uuid, uuid)
        clipboard.setClipEntry(clipData.toClipEntry())
    }
})

👉 Jason Ernst: Android ClipboardManager Deprecated: How to fix

 

🧑🏻‍💻 ViewModel 側でコピー処理をまとめる

世界的に著名な Android 開発者 Chris Banes や Jake Wharton のサンプルコードでは、UI 層から直接 Clipboard を操作せず、ViewModel に処理をまとめる パターンが多く見られます。

このアプローチを取ることで、UI の再コンポーズと Clipboard 操作が分離でき、よりテストしやすい設計になります。


class NoteViewModel : ViewModel() {
  fun copy(block: suspend () -> Unit) {
    viewModelScope.launch {
      block()
    }
  }
}

UI 側では以下のように呼び出せます:


IconButton(
  onClick = { 
    viewModel.copy {
      clipboard.setText(item.text)
    }
  }
) {
  Icon(Icons.Default.ContentCopy, contentDescription = null)
}

Clipboard 拡張関数を定義しておくと便利です。


suspend fun Clipboard.setText(text: String) { 
    val clipData = ClipData.newPlainText(text, text).toClipEntry() 
    setClipEntry(clipData)
}

 

🧑🏻‍💻 まとめ

ClipboardManager は非推奨 → LocalClipboard + suspend が公式推奨。

UI 層はイベントを投げるだけ、コピー処理は ViewModel で完結。

Coroutine scope を ViewModel 内で扱うことで UI の再コンポーズに影響しない。

ViewModel が clipboard を直接握るのは避けたほうがベター。
(非 UI 層に UI 依存を持ち込むことになるため)

拡張関数で共通処理化すれば再利用性が高まる。

つまり、

「UI はシンプルに」「コピー処理は ViewModel に集約」

これが現代的な Compose + Clipboard のベストプラクティスです。


Jetpack Compose 「Modifierは子1つ目だけに適用する」の考え方

実際のコード例で考えてみる。


Column {
  Text(
    text = "Hello",
    modifier = Modifier.padding(16.dp)
  )
  Text(
    text = "World",
    modifier = Modifier.padding(16.dp)
  )
}


val modifier = Modifier.padding(16.dp)

Column {
  Text(text = "Hello", modifier = modifier)
  Text(text = "World", modifier = modifier)
}


Column(
  modifier = Modifier.padding(16.dp)
) {
  Text(text = "Hello")
  Text(text = "World")
}


@Composable
fun ParentLayout() {
  Column(
    modifier = Modifier.verticalScroll(rememberScrollState())
  ) {
    for (i in 1..10) {
      ListItem(
        text = "Item $i",
        modifier = Modifier.padding(8.dp)
      )
    }
  }
}

@Composable
fun ListItem(modifier: Modifier, text: String) {
  Text(
    text = text,
    modifier = modifier.background(Color.LightGray)
  )
}

共有することの意味は「統一性」だろうけども、

見通しが悪くなるので「1つ目まで」としているのだろう。

「まずは個別につけてから共通部分をホイストしていく。」

「親から渡すときは子まで影響。孫は個別に。」

そんな考え方の順番が簡単で自然だと思います。