emacsでjqをJSONファイルのformatterとして使う
emacsでjqをJSONファイルのformatterとして使う。方法は2つ。
- shell-command-on-regionを使う
- formatter専用の関数を作る
shell-command-on-regionを使う
M-|
にshell-command-on-region
という関数がbindされている。これに引数を与えてあげると、現在選択されているリージョンを上書きしてくれる。
shell-command-on-region
の定義は以下の様になっている(引数の部分のみ)。
(defun shell-command-on-region (start end command &optional output-buffer replace error-buffer display-error-buffer region-noncontiguous-p) (interactive (let (string) (unless (mark) (user-error "The mark is not set now, so there is no region")) ;; Do this before calling region-beginning ;; and region-end, in case subprocess output ;; relocates them while we are in the minibuffer. (setq string (read-shell-command "Shell command on region: ")) ;; call-interactively recognizes region-beginning and ;; region-end specially, leaving them in the history. (list (region-beginning) (region-end) string current-prefix-arg current-prefix-arg shell-command-default-error-buffer t (region-noncontiguous-p)))) ... )
ここでreplaceの引数にnon-nilな値を与えてあげれば良い。例えば以下の様な形で使う。
C-u - 1 M-| # minibufferで jq . -S
formatter専用の関数を作る
shell-command-on-region
を使うのも便利なんだけれど。呼び出しが頻繁になってきた場合に以下の様な点で困る。
- エラーのときにもエラーメッセージで置換されてしまう
- そもそもjqとコマンドを打つのがめんどくさい
例えば、javascript-modeを拡張したjson-modeを自分で作ってあげて、拡張子が.json
のファイルを開いた場合には、この自作のjson-modeで開くようにする。
そして、この自作したmodeのkey-mapにテキトウなformmatter専用の関数を割り当てる。
(require 'derived) (define-derived-mode my:json-mode javascript-mode "json mode") (defun my:json-mode-setup () ;; 個人的にはバッファの保存は自動で行っているので`C-x C-s`に割り当ててしまっている (define-key my:json-mode-map (kbd "C-x C-s") 'my:jsonfmt) ) (add-to-list 'auto-mode-alist '("\\.json$" . my:json-mode)) (add-hook 'my:json-mode-hook 'my:json-mode-setup)
ここで、my:jsonfmtが自作したformatter関数。定義は以下の様な感じ。
(defun my:jsonfmt (beg end) (interactive "r") (unless (region-active-p) (setq beg (point-min)) (setq end (point-max)) ) (my:execute-formatter-command "jq" "jq . -S -e" beg end)) (defun my:get-fresh-buffer-create (name) (let ((buf (get-buffer-create name))) (with-current-buffer buf (setf (buffer-string) "")) buf )) (defun my:execute-formatter-command (cmd-name cmd beg end) (cond ((executable-find cmd-name) (save-excursion (save-restriction (narrow-to-region beg end) (let ((buf (my:get-fresh-buffer-create (format "*%s*" cmd-name))) (err-buf (my:get-fresh-buffer-create (format "*%s error*" cmd-name)))) (let ((status ;; xxx (flet ((display-message-or-buffer (&rest args) nil)) (shell-command-on-region (point-min) (point-max) cmd buf nil err-buf) ))) (cond ((= 0 status) (let ((replaced (with-current-buffer buf (buffer-string)))) (cond ((string= replaced "") (message "succeeded with no output")) (t (delete-region (point-min) (point-max)) (insert replaced))))) (t (message (with-current-buffer err-buf (buffer-string)))))))))) (t (message (format "%s is not found" cmd-name)))))
(メッセージを抑制するためにfletを使っているのだけれど。お行儀が良くない)
marshmallow-polyfieldを使ってoneOf的な構造のdataを扱う
はじめに
例えば、以下のよう1つのfieldに複数の形状の値が入ることがある。そして、その形状を決めるためにtype
などfieldを含まれているJSONがあるとする。
以下の様な感じ(下の例では、personとgroupという2つの形状がobに入る可能性がある)。
{ "ob": { "age": 20, "name": "foo" }, "type": "person" }
もしくはこう。
{ "ob": { "name": "A", "members": [ { "age": 20, "name": "foo" } ] }, "type": "group" }
それぞれ、typeで判別できるけれど。これを良い感じにmarshmallowでserialize,deserializeしたいという話し。
準備
事前に以下が必要。marshmallow-polyfieldを使う。
$ pip install marshmallow-polyfield
方法
以下の様な感じ。
from marshmallow import Schema, fields from marshmallow_polyfield import PolyField class Person(Schema): name = fields.String(required=True) age = fields.Integer(required=True) class Group(Schema): name = fields.String(required=True) members = fields.List(fields.Nested(Person()), required=True) def selector_for_deserialize(d, parent): if parent.get("type") == "group": return Group() else: return Person() def selector_for_serialize(ob, parent): if "members" in ob: parent["type"] = "group" return Group() else: parent["type"] = "person" return Person() class S(Schema): type = fields.String(required=True) ob = PolyField( serialization_schema_selector=selector_for_serialize, deserialization_schema_selector=selector_for_deserialize, required=True ) print(S().load({"ob": {"name": "foo", "age": 20}, "type": "person"})) print(S().load({"ob": {"name": "A", "members": [{"name": "foo", "age": 20}]}, "type": "group"})) print(S().dump({"ob": {"name": "foo", "age": 20}})) print(S().dump({"ob": {"name": "A", "members": [{"name": "foo", "age": 20}]}})) # UnmarshalResult(data={'ob': {'name': 'foo', 'age': 20}, 'type': 'person'}, errors={}) # UnmarshalResult(data={'ob': {'name': 'A', 'members': [{'name': 'foo', 'age': 20}]}, 'type': 'group'}, errors={}) # MarshalResult(data={'ob': {'name': 'foo', 'age': 20}, 'type': 'person'}, errors={}) # MarshalResult(data={'ob': {'name': 'A', 'members': [{'name': 'foo', 'age': 20}]}, 'type': 'group'}, errors={})