JSON, YAML, TOML などを加工や相互変換できるコマンドラインツール - dasel


## dasel とは

https://github.com/TomWright/dasel

> Dasel (short for data-selector) allows you to query and modify data structures using selector strings.
> Comparable to jq / yq, but supports JSON, YAML, TOML, XML and CSV with zero runtime dependencies.

-  [jq](https://github.com/stedolan/jq) や [yq](https://github.com/mikefarah/yq) と互換
- JSON も YAML も TOML も CSV も XML も
- Golang で書かれたシングルバイナリ

互換とは書かれているけど、セレクタの書き方は jq とはかなり違います。TOML への変換も TOML からの変換もサポートしているのが嬉しい。



## インストール

`brew install dasel`
or
`go install github.com/tomwright/dasel/cmd/dasel@master`
or
その他の方法はドキュメントをどうぞ → https://daseldocs.tomwright.me/installation




## 基本的な利用イメージ

`dasel -r yaml -w xml '.name'` → 「YAML の入力に対して `name` プロパティを抽出して XML で出力してください」



## JSON 入力での色々な例


### 整形するだけ

`dasel -r json`

```
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel -r json
{
  "members": [
    {
      "age": 14,
      "id": 0,
      "name": "Rei"
    },
    {
      "age": 14,
      "id": 1,
      "name": "Shinji"
    },
    {
      "age": 28,
      "cursed": true,
      "id": 2,
      "name": "Asuka"
    }
  ]
}
```


### `--color` オプションで色を付ける 

`dasel --color -r json`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json
{
  "members": [
    {
      "age": 14,
      "id": 0,
      "name": "Rei"
    },
    {
      "age": 14,
      "id": 1,
      "name": "Shinji"
    },
    {
      "age": 28,
      "cursed": true,
      "id": 2,
      "name": "Asuka"
    }
  ]
}
```


### セレクタの基本的な記法

#### `.propertyName` でマップのプロパティをたどり、 `.[n]` で配列のインデックスをたどる

`dasel --color -r json '.members.[1]'`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.[1]'
{
  "age": 14,
  "id": 1,
  "name": "Shinji"
}
```


### フォーマット変換

たぶん、これが一番便利でよく使う。

#### `-w yaml` オプションで出力を YAML に変換する

`dasel --color -r json -w yaml '.members.[2]'`

```yaml
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json -w yaml '.members.[2]'
age: 28
cursed: true
id: 2
name: Asuka
```


#### `-w toml` オプションで出力を TOML に変換する

`dasel --color -r json -w toml '.members.[2]'`

```toml
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json -w toml '.members.[2]'
age = 28.0
cursed = true
id = 2.0
name = "Asuka"
```


#### `-w xml` オプションで出力を XML に変換する

`dasel --color -r json -w xml '.members.[2]'`

```toml
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json -w xml '.members.[2]'
<doc>
  <age>28</age>
  <cursed>true</cursed>
  <id>2</id>
  <name>Asuka</name>
</doc>
```


### `.(<key>=<value>)` でフィルタリング

#### `.(age=14)` として、 14 歳の2人だけに絞り込み。

`dasel --color -r json '.members.(age=14)' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.(age=14)' -m
{
  "age": 14,
  "id": 0,
  "name": "Rei"
}
{
  "age": 14,
  "id": 1,
  "name": "Shinji"
}
```


#### `.(age>=20)` として、20歳以上の大人のみに絞り込む

`dasel --color -r json '.members.(age>=20)' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.(age>=20)' -m
{
  "age": 28,
  "cursed": true,
  "id": 2,
  "name": "Asuka"
}
```

比較演算子は `=` と `>=` のほかに `>`, `<`, `<=`, `!=` と一通りがあります。


#### フィルタリングした結果をさらに `.` でたどる

`dasel --color -r json '.members.(age=14).name' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.(age=14).name' -m
"Rei"
"Shinji"
```

厳密にいうと、フィルタリングと呼んだ `.(<key>=<value>)` は **ダイナミックセレクタ** という、特殊だけどセレクタの1種です。


#### フィルタリング条件を複数並べると AND 条件となる

#### `.(age<20)(id>0)` として、未成年で id が正の数のメンバーを抽出。

`dasel --color -r json '.members.(age<20)(id>0)' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.(age<20)(id>0)' -m
{
  "age": 14,
  "id": 1,
  "name": "Shinji"
}
```


### 色々なセレクタの記法

#### `.-` セレクタでマップの Key 一覧を出力

`dasel --color -r json '.members.[1].-' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.[1].-' -m
"id"
"name"
"age"
```

`.-` セレクタは **配列** の場合は Index 一覧が出力される。
`dasel --color -r json '.members.-' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.-' -m
"0"
"1"
"2"
```

※ `.-` セレクタは `-m` (`--multiple`) オプションの使用が必須なので注意。


#### `.[*]` セレクタでマップの Value 一覧を出力

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.[1].[*]' -m
1
"Shinji"
14
```

`.[*]` セレクタは **配列** の場合も Value 一覧が出力される。
`dasel --color -r json '.members.[*]' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.[*]' -m
{
  "age": 14,
  "id": 0,
  "name": "Rei"
}
{
  "age": 14,
  "id": 1,
  "name": "Shinji"
}
{
  "age": 28,
  "cursed": true,
  "id": 2,
  "name": "Asuka"
}
```

※ `.[*]` セレクタも `-m` (`--multiple`) オプションの使用が必須なので注意。


#### `.[#]` セレクタで要素数を出力

`dasel --color -r json '.members.(age<20)(id>0).[#]' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.(age<20)(id>0).[#]' -m
3
```

- 配列の場合は、そのまま要素数
- マップの場合は、プロパティ数
- 文字列の場合は、文字数

`.[#]` セレクタじゃなくて `--lenghth` オプションでも同様に要素数が得られる。
`dasel --color -r json '.members.(age<20)(id>0)' -m --length`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.(age<20)(id>0)' -m --length
3
```


#### `.[@]` でデータ型を取得する

`dasel --color -r json '.members.[@]' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel --color -r json '.members.[@]' -m
"array"
```

- 複合的な型は `array`, `map` の２つ
- スカラ型は `string`, `int`, `float`, `bool` の４つ 


### 高度なセレクタ

#### `.(?:<key>=<value>)` で再帰的な検索

※ `-m` (`--multiple`) オプションの使用が必須なので注意。

例を少し複雑にして `friends` プロパティに `"Hikari"` さんを追加します。 入力 JSON の全体構造は次のとおり。

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true, "friends": [{"name": "Hikari", "age": 14}]}]}' \
 | dasel --color -r json -m
{
  "members": [
    {
      "age": 14,
      "id": 0,
      "name": "Rei"
    },
    {
      "age": 14,
      "id": 1,
      "name": "Shinji"
    },
    {
      "age": 28,
      "cursed": true,
      "friends": [
        {
          "age": 14,
          "name": "Hikari"
        }
      ],
      "id": 2,
      "name": "Asuka"
    }
  ]
}
```


#### `.(?:.age=14)` として、14歳のオブジェクト（マップ）を再帰的に検索

`dasel --color -r json '.members.(?:age=14).name' -m`

```json
 echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true, "friends": [{"name": "Hikari", "age": 14}]}]}' \
 | dasel --color -r json '.members.(?:age=14).name' -m
"Rei"
"Shinji"
"Hikari"
```

`.members.(?:age=14).name` ではなく、
`.members.(age=14).name` というふうに単なるダイナミックセレクタにしてしまうと、再帰的には処理されないのが違い。
次の例では `"Hikari"` は１段ネストした先にいるので、出力されていないことが分かる。

`dasel --color -r json '.members.(age=14).name' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true, "friends": [{"name": "Hikari", "age": 14}]}]}' \
 | dasel --color -r json '.members.(age=14).name' -m
"Rei"
"Shinji"
```

`.(?:<key>=<value>)` の検索で使える演算子は `=` と `!=` のみ。


### `put` サブコマンドで要素の追加や更新をする

`dasel put <type> <selector> <value>` のような感じでデータ型の指定と 追加/更新 要素の値を渡す。ちなみにサブコマンド無指定の場合は、実は暗黙的に `select` サブコマンドが使われている。なのでこれまでの例は全て `select` サブコマンドを明示的に指定しても同じ。

※ `put` サブコマンドでは `-f`/`--file` で入力ファイル指定をした場合に、`-o`/`--out` を使って出力先を指定していないと、入力ファイル自体を更新してしまう（破壊的！）ので注意。 ファイルではなく標準出力に吐きたい場合は `-f stdout`/`-f -` で OK。

#### `int '.members.[*].age' 20 -m` として、全員の年齢を２０歳に更新

`dasel put -r json int '.members.[*].age' 20 -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel put -r json int '.members.[*].age' 20 -m
{
  "members": [
    {
      "age": 20,
      "id": 0,
      "name": "Rei"
    },
    {
      "age": 20,
      "id": 1,
      "name": "Shinji"
    },
    {
      "age": 20,
      "cursed": true,
      "id": 2,
      "name": "Asuka"
    }
  ]
}
```


#### `int '.members.(age<20).age' 20 -m` として、未成年のみ全員を２０歳に更新

`dasel put -r json int '.members.(age<20).age' 20 -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel put -r json int '.members.(age<20).age' 20 -m
{
  "members": [
    {
      "age": 20,
      "id": 0,
      "name": "Rei"
    },
    {
      "age": 20,
      "id": 1,
      "name": "Shinji"
    },
    {
      "age": 28,
      "cursed": true,
      "id": 2,
      "name": "Asuka"
    }
  ]
}
```


#### `bool '.members.(age<20).is_young' true -m` として、未成年のオブジェクト（マップ）に `"is_young": true` プロパティをセット

`dasel put -r json bool '.members.(age<20).is_young' true -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel put -r json bool '.members.(age<20).is_young' true -m
{
  "members": [
    {
      "age": 14,
      "id": 0,
      "is_young": true,
      "name": "Rei"
    },
    {
      "age": 14,
      "id": 1,
      "is_young": true,
      "name": "Shinji"
    },
    {
      "age": 28,
      "cursed": true,
      "id": 2,
      "name": "Asuka"
    }
  ]
}
```


#### `document '.members.[]' '{"name": "Kaworu"}' -m` として、配列の末尾に新しいメンバーを追加

`.[]` のセレクタは　`put` サブコマンドでしか使えないので注意。
 jq で `.members[]` とするセレクタは dasel では `.members.[*]` となる。

`dasel put -r json document '.members.[]' '{"name": "Kaworu"}' -m`

```json
echo '{"members": [{"id": 0, "name": "Rei", "age": 14}, {"id": 1, "name": "Shinji", "age": 14}, {"id": 2, "name": "Asuka", "age": 28, "cursed": true}]}' \
 | dasel put -r json document '.members.[]' '{"name": "Kaworu"}' -m
{
  "members": [
    {
      "age": 14,
      "id": 0,
      "name": "Rei"
    },
    {
      "age": 14,
      "id": 1,
      "name": "Shinji"
    },
    {
      "age": 28,
      "cursed": true,
      "id": 2,
      "name": "Asuka"
    },
    {
      "name": "Kaworu"
    }
  ]
}
```




## おまけ： select サブコマンドのオプション一覧

|  |  |  |
|:-----|:---|:---|
| `-s`   | `--selector string` | データ構造をクエリするときに使用するセレクタ。明示しない場合は第一引数を勝手にセレクタと解釈してくれるので、必須指定だけどあまり使わない。 |
| `-r`   | `--read string` | **読み込み** に使用するパーサー。 書いてる時点でサポートされているのは `json`, `toml`, `yaml`, `xml`, `csv`, `plain`  |
| `-w`   | `--write string` | **書き込み** に使用するパーサー。 書いてる時点でサポートされているのは `json`, `toml`, `yaml`, `xml`, `csv`, `plain` |
| `-p`   | `--parser string` | -r と -w をまとめて指定したのと同じ。 |
|      | `--colour` | 色付け表示。 |
|      | `--color` | --colour の別名。 |
| `--c`  |  `--compact` | できる限りプリティプリントを除去して出力をコンパクトにする。 |
|      | `--escape-html` |  出力を書くときに HTML タグをエスケープする。 |
| `--f`  |  `--file string` |  クエリするファイル。 |
|      | `--format string` | 結果を書くときに使用するフォーマットテンプレート。 |
|      | `--length` | 結果の要素数を数字で出力。 |
|      | `--merge-input-documents` | 複数の入力文書を配列に結合。 |
| `-m`   | `--multiple` | 複数の結果を選択。単一の結果の場合にエラーになるわけでもなく基本的に常に指定しておけばいい気がする。 |
| `-n`   | `--null` | 値が見つからないエラーの代わりに null を出力。 |
|      | `--plain` | -w plain の別名。 |
| `-h`   | `--help` | 選択した項目のヘルプ |


## 個人的な Tips

- `-m` は常時指定でいいと思う。結果が複数のときに指定してないと最初の１つだけになってしまうので。
- `put` の場合は `-f`や`--file`は使わず、パイプで入力を渡すほうが安心。`-o`/`--out` の指定忘れで意図せず入力元ファイルを更新されるのを防げるから。
- クエリは jq や yq のほうが使いやすい気がする（慣れてるだけかも）。フォーマット変換が必要なときにパイプでつないで 　dasel を使う、とか組み合わせて使う前提が良さそう。dasel は TOML に変換できるのがありがたい。