TL; DR
json
$ cat sample.json | jq '(.. | select(type == "string")) |= ""'
{
"foo": "",
"bar": {
"baz": "",
"bool": true
},
"num": 42,
"arr": [
"",
"",
{
"three": ""
}
]
}
yaml
$ cat sample.yaml | yq '(.. | select(tag == ("" | tag))) |= ""'
foo: ""
bar:
baz: ""
bool: true
num: 42
arr:
- ""
- ""
- three: ""
やりたいこと
JSONやYAMLで書かれた設定ファイルのバリデーションチェックをテストするとき、「全部の値が空文字になったテストケースがあれば楽なのに」と思ったので作りました。
単純に値を空文字にすると深い階層が読み込めなくなってしまうので、スキーマを保ったまま文字列の値だけを空文字に置換するようにしています。
これを...
{
"foo": "FOO",
"bar": {
"baz": "BAZ"
}
}
こうしたい
{
"foo": "",
"bar": {
"baz": ""
}
}
階層が消えるのは困る
{
"foo": "",
"bar": ""
}
JSONの空文字化
jq を使ってJSONを処理します。
$ jq --version
jq-1.6
オブジェクト内の文字列値を再帰的に探索し、それらを空文字列に置き換えることで実現しました。
json
$ cat sample.json
{
"foo": "FOO",
"bar": {
"baz": "BAZ",
"bool": true
},
"num": 42,
"arr": [
"one",
"two",
{"three": "THREE"}
]
}
$ cat sample.json | jq '(.. | select(type == "string")) |= ""'
{
"foo": "",
"bar": {
"baz": "",
"bool": true
},
"num": 42,
"arr": [
"",
"",
{
"three": ""
}
]
}
コードの意味は以下の通りです。
(
# オブジェクト内の全値を再帰的に取得 (Recursive Descent)
..
# 文字列型のみ抽出
| select(type == "string")
)
# 上記で抽出できた各値を "" で上書き (Update-assignment)
|= ""
YAMLの空文字化
こちらでは yq を使用しました。
$ yq --version
yq (https://github.com/mikefarah/yq/) version 4.20.2
書き方はJSONの場合と同じです。ただし、yqには type
関数が無いため、代わりに tag
を用いて文字列型の抽出を行っています。
$ cat sample.yaml
foo: FOO
bar:
baz: BAZ
bool: true
num: 42
arr:
- "one"
- "two"
- three: "THREE"
$ cat sample.yaml | yq '(.. | select(tag == ("" | tag))) |= ""'
foo: ""
bar:
baz: ""
bool: true
num: 42
arr:
- ""
- ""
- three: ""
tag
関数は式のタグを取得する関数で、デフォルトでは型名が取得できます。
$ echo "a: foo" | yq '.a | tag'
!!str
ただし、!!str
のリテラルをべた書きすることはできないので、タグは第一級オブジェクトではなさそうです。
仕方がないので、上記のように「 .
のタグが ""
のタグと等しい」という回りくどい比較を行っています(解決策をご存じの方はコメントいただけるとありがたいです)。
おわりに
jq/yqは本当に何でもできると改めて実感しました。早く極めてシェル芸人になりたい