dictknife,jsonknifeの機能を整理
はじめに
json,yamlを扱う上で便利そうな機能を含んだ十得ナイフっぽい感じのライブラリを作っていた。それにコマンドをもたせた結果便利になってきたのでどういうときに何を使うかを整理しておく(完全に自分用のメモ)。
dictknife
dictknifeとjsonknifeという2つのコマンドを用意している。jsonknifeはjsonschemaやswaggerというかJSON関連の諸々に関連した機能を持っている。一方dictknifeはもう少しprimitiveなもの。
提供しているコマンドは以下
Usage: dictknife [OPTIONS] COMMAND [ARGS]... Options: --log [NOTSET|ERROR|WARNING|WARN|INFO|DEBUG|CRITICAL] logging level -h, --help Show this message and exit. Commands: concat concat dicts diff diff dict transform transform dict
特に機能を網羅して紹介や説明をするという気も起きないし。あんまり意味が無い気がするのでどういうふうに使うかだけ書く。
concat
concatはその名前の通りに何かと何かを結合させるためのものではある。例えば以下の様な感じに。
$ dictknife concat --format json <(echo '{"name": "foo"}') <(echo '{"age": 20}') { "name": "foo", "age": 20 }
ふつうのconcatと異なるのはネストした表現にも対応しているところ。
$ cat <<-EOS > person.yaml definitions: person: type: object properties: name: type: string age: type: integer EOS $ cat <<-EOS > group.yaml definitions: group: type: object properties: members: type: array items: "#/definitions/person" EOS $ dictknife concat person.yaml group.yaml definitions: person: type: object properties: name: type: string age: type: integer group: type: object properties: members: type: array items: '#/definitions/person'
フォーマットを変換したい場合に
concatという名前の通り元々のファイルをつなげる事が目的のコマンドだった。とは言え1つのものに対してフォーマットを変えたい場合にも使う。意外と便利。 --dst
で拡張子を付けても良いし。 –output-formatで出力するformatを変えても良い。
$ dictknife concat --output-format yaml --input-format json <(echo '{"name": "foo"}') <(echo '{"age": 20}') name: foo age: 20 $ dictknife concat --output-format toml --input-format json <(echo '{"name": "foo"}') <(echo '{"age": 20}') name = "foo" age = 20
対応している拡張子は今のところ.yaml(.yml),.json.toml。
diff
diffはその名の通りdiff。ソートした結果に対してdiffを調べてくれるので順序の処理が甘い感じのコマンドの出力結果を調べるのに便利。
$ cat <<-EOS > person0.yaml person: name: foo age: 20 EOS $ cat <<-EOS > person1.yaml person: age: 20 name: foo EOS $ dictknife diff person{0,1}.yaml
もちろんふつうのdiffでは差分が存在する。
$ diff -u person{0,1}.yaml --- person0.yaml 2017-05-27 22:59:26.000000000 +0900 +++ person1.yaml 2017-05-27 22:58:47.000000000 +0900 @@ -1,3 +1,3 @@ person: - name: foo age: 20 + name: foo
deserializeされた後のsictに対するdiffを見るので順序には依存していない。ただ出力結果がJSONになっているのがちょっと不便といえば不便かもしれない。
$ cat <<-EOS > person2.yaml person: age: 20 name: bar nickname: b EOS $ dictknife diff person{0,2}.yaml --- person0.yaml +++ person2.yaml @@ -1,6 +1,7 @@ { "person": { "age": 20, - "name": "foo" + "name": "bar", + "nickname": "b" } }
あと、昔の実装では単にjson.dumps時にsort_keysをTrueにすれば良いだろうとだけ思っていたのだけれど。そうでもない。例えば、yaml上の表現としてkeyに数値が許されるのだけれど。JSON上はkeyは文字列と規程されている。そのため以下の様なデータを渡したとき失敗していた。
$ cat <<-EOS > status.yaml 200: ok default: hmm EOS $ dictknife diff status.yaml status.yaml TypeError: unorderable types: str() < int()
これに --normalize
オプションを付けてあげるとkeyを文字列に変換してから比較してくれるので問題がなくなる。
$ dictknife diff --normalize status.yaml status.yaml
transform
dictknife transformはその名の通り変換。よく使うのは --code
でのeval的な記述。
$ cat status.yaml 200: ok default: hmm $ cat status.yaml | dictknife transform --code='lambda d: [d,d,d]' - 200: ok default: hmm - 200: ok default: hmm - 200: ok default: hmm
上の例では特に意味のない変換だったけれど。昔はjsonknife derefとの組み合わせで使う事があった(今ではunwrap,wrapのペアやrefの指定方法の進化で不要になった)。
これが必要になったらある意味負け。ただloadとdumpを繰り返すような変換処理があった場合にこれ経由で呼びだすと便利みたいなことはある。
$ cat <<-EOS > upcase.py def upcase(d): if hasattr(d, "keys"): return {upcase(k): upcase(v) for k, v in d.items()} elif isinstance(d, (list, tuple)): return [upcase(d) for e in d] else: return str(d).upper() EOS $ cat <<-EOS > person.yaml definitions: person: type: object properties: name: type: string age: type: integer EOS $ cat person.yaml | dictknife transform --function "./upcase.py:upcase" DEFINITIONS: PERSON: PROPERTIES: AGE: TYPE: INTEGER NAME: TYPE: STRING TYPE: OBJECT