summaryrefslogtreecommitdiffhomepage
path: root/vhosts/blog/public/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html
blob: 38a52e9c71b576f2e58f282e47978b0eacf67da4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
<!DOCTYPE html>
<html lang="ja-JP">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="nsfisis">
    <meta name="copyright" content="&copy; 2024 nsfisis">
    <meta name="description" content="文法エラーだけを直し、空白の削除や挿入といった整形処理を一切おこなわない JSON フォーマッタを作成した。Neovim と連携させる設定例も紹介する。">
    <meta name="keywords" content="Neovim,Vim">
    <meta property="og:type" content="article">
    <meta property="og:title" content="reparojson: 文法エラーを直すだけの JSON フォーマッタを作った|REPL: Rest-Eat-Program Loop">
    <meta property="og:description" content="文法エラーだけを直し、空白の削除や挿入といった整形処理を一切おこなわない JSON フォーマッタを作成した。Neovim と連携させる設定例も紹介する。">
    <meta property="og:site_name" content="REPL: Rest-Eat-Program Loop">
    <meta property="og:locale" content="ja_JP">
    <link rel="icon" type="image/svg+xml" href="/favicon.svg">
    <title>reparojson: 文法エラーを直すだけの JSON フォーマッタを作った|REPL: Rest-Eat-Program Loop</title>
    <link rel="stylesheet" href="/style.css?h=8415c13be008f55a351b69c568b3e8ed">
  </head>
  <body class="single">
    <header class="header">
      <div class="site-logo">
        <a href="/">REPL: Rest-Eat-Program Loop</a>
      </div>
      <nav class="nav">
        <ul>
          <li>
            <a href="/about/">About</a>
          </li>
          <li>
            <a href="/posts/">Posts</a>
          </li>
          <li>
            <a href="/slides/">Slides</a>
          </li>
          <li>
            <a href="/tags/">Tags</a>
          </li>
        </ul>
      </nav>
    </header>
    <main class="main">
      <article class="post-single">
        <header class="post-header">
          <h1 class="post-title">reparojson: 文法エラーを直すだけの JSON フォーマッタを作った</h1>
          <ul class="post-tags">
            <li class="tag">
              <a href="/tags/neovim/">Neovim</a>
            </li>
            <li class="tag">
              <a href="/tags/vim/">Vim</a>
            </li>
          </ul>
        </header>
        <div class="post-content">
          <section id="changelog">
            <h2><a href="#changelog">更新履歴</a></h2>
            <ol>
              <li class="revision">
                <time datetime="2024-07-19">2024-07-19</time>: 公開
              </li>
            </ol>
          </section>
          <div class="admonition">
            <div class="admonition-label">
              NOTE
            </div>
            <div class="admonition-content">
              <p>
                この記事は <a href="https://vim-jp.org/ekiden/" rel="noreferrer" target="_blank">Vim 駅伝</a> #218 の記事です。
              </p>
            </div>
          </div>
          <section id="section--intro">
            <h2><a href="#section--intro">欲しかったもの</a></h2>
            <p>
              Vim で JSON を編集しているときに、文法エラー (末尾カンマやカンマの不足) のみを修正して一切の整形をおこなわないプラグインが欲しかった。 整形も同時におこなうプラグインは見つかっただけでも多数あったのだが、整形しないものは見つけられなかったので自作することにした。
            </p>
            <p>
              なお、作成したツール自体は単体の CLI として動作し、Vim とは無関係に使うことができる。 この記事では Neovim と組み合わせる場合の設定を紹介するが、およそ任意のエディタで使えるだろう。
            </p>
          </section>
          <section id="section--reparojson">
            <h2><a href="#section--reparojson">作ったもの</a></h2>
            <p>
              作成したものがこちら: <a href="https://github.com/nsfisis/reparojson" rel="noreferrer" target="_blank">ReparoJSON</a>
            </p>
            <p>
              次のように動作する。
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span>$ echo '[ 1 2 ]' | reparojson</span></span>
<span class="line"><span>[ 1, 2 ]</span></span>
<span class="line"><span></span></span>
<span class="line"><span>$ echo '[ 1, 2, ]' | reparojson</span></span>
<span class="line"><span>[ 1, 2 ]</span></span>
<span class="line"><span></span></span>
<span class="line"><span>$ echo '{ "foo": 1 "bar": 2 }' | reparojson</span></span>
<span class="line"><span>{ "foo": 1, "bar": 2 }</span></span>
<span class="line"><span></span></span>
<span class="line"><span>$ echo '{ "foo": 1, "bar": 2, }' | reparojson</span></span>
<span class="line"><span>{ "foo": 1, "bar": 2 }</span></span></code></pre>
            </div>
            <p>
              バージョン 0.1.1 時点で修正対象の文法エラーは次のとおり:
            </p>
            <ul>
              <li>
                配列末尾の余計なカンマ (削除する)
              </li>
              <li>
                配列内のカンマ不足 (挿入する)
              </li>
              <li>
                オブジェクト末尾の余計なカンマ (削除する)
              </li>
              <li>
                オブジェクト内のカンマ不足 (挿入する)
              </li>
            </ul>
            <p>
              他にも自動で直せそうなエラーはいくつか思いつくが (オブジェクトのキーがクォートされていない等)、私自身があまり困っていないので優先度は低い。
            </p>
          </section>
          <section id="section--itegration-with-neovim">
            <h2><a href="#section--itegration-with-neovim">Neovim との連携</a></h2>
            <p>
              Neovim で JSON ファイルを保存したときに、上記のツールを自動で走らせるように設定する。
            </p>
            <p>
              ここでは、 <a href="https://github.com/neovim/nvim-lspconfig" rel="noreferrer" target="_blank">nvim-lspconfig</a> と <a href="https://github.com/mattn/efm-langserver" rel="noreferrer" target="_blank">efm-langserver</a> を用いた設定例を紹介する。
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#D73A49">   local</span><span style="color:#24292E"> lspconfig </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">'lspconfig'</span><span style="color:#24292E">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E">   lspconfig.</span><span style="color:#6F42C1">efm</span><span style="color:#24292E">.</span><span style="color:#005CC5">setup</span><span style="color:#24292E">({</span></span>
<span class="line"><span style="color:#24292E">      init_options </span><span style="color:#D73A49">=</span><span style="color:#24292E"> { documentFormatting </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> true</span><span style="color:#24292E"> },</span></span>
<span class="line"><span style="color:#24292E">      settings </span><span style="color:#D73A49">=</span><span style="color:#24292E"> {</span></span>
<span class="line"><span style="color:#24292E">         rootMarkers </span><span style="color:#D73A49">=</span><span style="color:#24292E"> {</span><span style="color:#032F62">".git/"</span><span style="color:#24292E">},</span></span>
<span class="line"><span style="color:#24292E">         languages </span><span style="color:#D73A49">=</span><span style="color:#24292E"> {</span></span>
<span class="line"><span style="color:#24292E">            json </span><span style="color:#D73A49">=</span><span style="color:#24292E"> {</span></span>
<span class="line"><span style="color:#24292E">               {</span></span>
<span class="line"><span style="color:#24292E">                  formatCommand </span><span style="color:#D73A49">=</span><span style="color:#032F62"> "reparojson -q"</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#24292E">                  formatStdin </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> true</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#24292E">               },</span></span>
<span class="line"><span style="color:#24292E">            },</span></span>
<span class="line"><span style="color:#24292E">         },</span></span>
<span class="line"><span style="color:#24292E">      }</span></span>
<span class="line"><span style="color:#24292E">   })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E">   vim.</span><span style="color:#6F42C1">api</span><span style="color:#24292E">.</span><span style="color:#005CC5">nvim_create_autocmd</span><span style="color:#24292E">(</span><span style="color:#032F62">'LspAttach'</span><span style="color:#24292E">, {</span></span>
<span class="line"><span style="color:#6F42C1">      callback</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> function</span><span style="color:#24292E">(e)</span></span>
<span class="line"><span style="color:#24292E">         vim.</span><span style="color:#6F42C1">api</span><span style="color:#24292E">.</span><span style="color:#005CC5">nvim_create_autocmd</span><span style="color:#24292E">(</span><span style="color:#032F62">'BufWritePre'</span><span style="color:#24292E">, {</span></span>
<span class="line"><span style="color:#24292E">            buffer </span><span style="color:#D73A49">=</span><span style="color:#24292E"> e.</span><span style="color:#6F42C1">buf</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#6F42C1">            callback</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> function</span><span style="color:#24292E">()</span></span>
<span class="line"><span style="color:#24292E">               vim.</span><span style="color:#6F42C1">lsp</span><span style="color:#24292E">.</span><span style="color:#6F42C1">buf</span><span style="color:#24292E">.</span><span style="color:#005CC5">format</span><span style="color:#24292E">({ async </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> false</span><span style="color:#24292E"> })</span></span>
<span class="line"><span style="color:#D73A49">            end</span></span>
<span class="line"><span style="color:#24292E">         })</span></span>
<span class="line"><span style="color:#D73A49">      end</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#24292E">   })</span></span></code></pre>
            </div>
            <p>
              ほとんどは nvim-lspconfig と efm-langserver を使う際のボイラープレートだが、<code>formatCommand</code> で <code>-q</code> フラグを指定していることに注意してほしい。 このツールは、デフォルトでは JSON が修正された場合 exit code 1 で終了する。 これは、入力が最初から正しかった場合と修正して正しくなった場合を区別するためだが、異常終了してしまうと置き換えが発生しない。 そのため、<code>-q</code> フラグを指定して、修正されたときも exit code 0 で終了するようにしている。
            </p>
          </section>
          <section id="section--outro">
            <h2><a href="#section--outro">おわりに</a></h2>
            <p>
              このツールが威力を発揮するのは、行の入れ換え時である。次のような JSON があり、
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">   {</span></span>
<span class="line"><span style="color:#005CC5">      "a"</span><span style="color:#24292E">: </span><span style="color:#005CC5">true</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#005CC5">      "b"</span><span style="color:#24292E">: </span><span style="color:#005CC5">false</span></span>
<span class="line"><span style="color:#24292E">   }</span></span></code></pre>
            </div>
            <p>
              2行目と3行目を入れ換えて以下のように編集した。
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">   {</span></span>
<span class="line"><span style="color:#005CC5">      "b"</span><span style="color:#24292E">: </span><span style="color:#005CC5">false</span></span>
<span class="line"><span style="color:#032F62">      "a"</span><span style="color:#B31D28;font-style:italic">:</span><span style="color:#005CC5"> true</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#24292E">   }</span></span></code></pre>
            </div>
            <p>
              これは不正な JSON だが、このツールを通せば次のようになる。
            </p>
            <div class="codeblock">
              <pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#24292E">   {</span></span>
<span class="line"><span style="color:#005CC5">      "b"</span><span style="color:#24292E">: </span><span style="color:#005CC5">false</span><span style="color:#24292E">,</span></span>
<span class="line"><span style="color:#005CC5">      "a"</span><span style="color:#24292E">: </span><span style="color:#005CC5">true</span></span>
<span class="line"><span style="color:#24292E">   }</span></span></code></pre>
            </div>
            <p>
              もちろん、このような操作を文法を壊さずにおこなう Vim プラグインは存在する。 しかし、単なる行の入れ換えであれば <code>ddp</code> の3ストロークでおこなうことができ、専用のキーバインドを覚える必要もない。 このツールを用いることで、より Vimmer-friendly な JSON 編集が可能となる。
            </p>
          </section>
        </div>
      </article>
    </main>
    <footer class="footer">
      &copy; 2021 nsfisis
    </footer>
  </body>
</html>