5
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 1 year has passed since last update.

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

Last updated at Posted at 2021-12-22

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.

  • jqyq と互換
  • 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

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]'

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]'

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]'

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]'

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

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

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

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

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

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

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 一覧を出力

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

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

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

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

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 の2つ
  • スカラ型は string, int, float, bool の4つ

高度なセレクタ

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

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

例を少し複雑にして friends プロパティに "Hikari" さんを追加します。 入力 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

 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" は1段ネストした先にいるので、出力されていないことが分かる。

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

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 として、全員の年齢を20歳に更新

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

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 として、未成年のみ全員を20歳に更新

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

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

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

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

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
5
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?