読者です 読者をやめる 読者になる 読者になる

ちょっとしたswagger spec(yaml, json)の取り扱いというか変換というか

過去に自分が作ったやつの使い方について。もう少しマシなものを考えてみたのでメモ。

github.com

yaml上のデータから一部分を取り出したい

こういうyamlがあるとする。swagger specの一部のようなもの。people,person,name,ageが定義されている。

# definitions.yaml

definitions:
  people:
    items:
      $ref: "#/definitions/person"
  person:
    properties:
      name:
        $ref: "#/definitions/name"
      age:
        $ref: "#/definitions/age"
  name:
    type: string
    description: 名前
  age:
    type: integer
    maximum: 100

この定義に$refによる参照が使われている。この$refを取り除くためにderefというコマンドを作っていた。

$ jsonknife deref --src definitions.yaml

結果はこうなる。

definitions:
  people:
    items:
      properties:
        name:
          type: string
          description: 名前
        age:
          type: integer
          maximum: 100
  person:
    properties:
      name:
        type: string
        description: 名前
      age:
        type: integer
        maximum: 100
  name:
    type: string
    description: 名前
  age:
    type: integer
    maximum: 100

全てが展開される。この内personだけが欲しい場合には --ref を使う。

$ jsonknife deref --src definitions.yaml --ref "#/definitions/person"

こういう結果になる。

properties:
  name:
    type: string
    description: 名前
  age:
    type: integer
    maximum: 100

取り出した結果をwrapしたい場合

--ref によりyamlのデータの中から一部だけ取り出せはしたものの。 #/definitions/person の位置にあるデータを取り出すということになってしまう。 今までこういう時に再度 definitions/person でラップするために、別のコマンドに渡していた。以下の様な形。

$ jsonknife deref --src definitions.yaml --ref "#/definitions/person" | dictknife transform --code 'lambda d: {"definitions": {"PERSON": d}}'

結果はこう。

definitions:
  PERSON:
    properties:
      name:
        type: string
        description: 名前
      age:
        type: integer
        maximum: 100

もちろん、結果は期待通りなのだけれど。わざわざこの種の変換を手書きするのも面倒くさい。単に包むだけなのだからオプションにしてしまえば良い。元の階層に復元する処理をwrapと呼ぶとすればrefで取り出すことはunwrapにほかならない。そんなわけでwrapとunwrapで指定できるようにしてみると良いかもしれない。

$ jsonknife deref --src definitions.yaml --unwrap "#/definitions/person" --wrap "#/definitions/PERSON"
definitions:
  PERSON:
    properties:
      name:
        type: string
        description: 名前
      age:
        type: integer
        maximum: 100

複数の値を同時に返す場合

全体に対するある部分の内、複数の部分の値が欲しい場合がある。例えば、nameとageを名前付きで欲しい場合。すごく真面目にやるならconcatを使う形になる。 プロセス置換なども使ってすごく頑張った見た目になるけれど。これで上手くいく。

$ dictknife concat <(jsonknife deref --src definitions.yaml --unwrap "#/definitions/name" --wrap "name")  <(jsonknife deref --src definitions.yaml --unwrap "#/definitions/age" --wrap "age")
name:
  type: string
  description: 名前
age:
  type: integer
  maximum: 100

ところで、--ref は複数渡せた。過去には --with-name というオプションがあり。これによって名前でラッピングされた結果がマージされて返されていた(今は存在していないオプション)。上と同様のものを以下の様に書けた。ただ元の名前でラップするだけなので全くおんなじというわけではない。

# 今は動かない
jsonknife deref --src definitions.yaml --ref "#/definitions/name" --ref "#/definitions/age" --with-name

ここでdefinitionsで包みたい場合に結局またdictknife transformによる同様の変換が必要になる。またwrap,unwrapの対応を数を数えてペアにするというのも馬鹿馬鹿しい。結局以下の様に書けるようにした。unwrapとwrapのペアを@でつなげるという記法。

jsonknife deref --src definitions.yaml --ref "#/definitions/name@#/definitions/NAME" --ref "#/definitions/age@#/definitions/AGE"

結果はこう。

definitions:
  AGE:
    type: integer
    maximum: 100
  NAME:
    type: string
    description: 名前