tag:crieit.net,2005:https://crieit.net/users/orangain/feed かとの投稿 - Crieit Crieitでユーザーかとによる最近の投稿 2020-07-06T22:33:40+09:00 https://crieit.net/users/orangain/feed tag:crieit.net,2005:PublicArticle/15996 2020-07-05T23:38:19+09:00 2020-07-06T22:33:40+09:00 https://crieit.net/posts/Semantic-UI-React-Form-Input Semantic UI ReactのForm.Inputとはなんなのか <p><a target="_blank" rel="nofollow noopener" href="https://react.semantic-ui.com/">Semantic UI React</a> を使っていて <code>Input</code> と <code>Form.Input</code> の違いがよくわかっていなかったので、ちゃんと調べてみました。</p> <p>バージョンは次のとおりです。</p> <ul> <li>React: 16.13.1</li> <li>Semantic UI React: 0.88.2</li> </ul> <h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://react.semantic-ui.com/collections/form/">リファレンス</a> で <code>Form.Input</code> を選択すると書かれている通り、 <code>Form.Input</code> は <code><Form.Field control={Input} /></code> のシンタックスシュガーです。</p> <blockquote> <p>Sugar for <code><Form.Field control={Input} /></code>.</p> </blockquote> <p>さらに <code>From.Field</code> の項目を読むと、次のように書かれています。</p> <blockquote> <p>A field is a form element containing a label and an input.</p> </blockquote> <p>しかし、バリデーションエラー用のLabelも重要な役割を担っているので、 <code>From.Field</code> は次の3つを含む複合コンポーネントだと捉えた方が良いでしょう <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> 。</p> <ul> <li>項目名を表すlabel</li> <li>メインの入力欄であるInput</li> <li>バリデーションエラー用のLabel</li> </ul> <p>さて、 <code>Form.Field</code> の中身を記述する方法は、大きく分けて次の2つがあります。</p> <ul> <li><code>control</code> を指定する方法( <code>Form.Input</code> を使ったときに発生)</li> <li><code>children</code> (子要素)を指定する方法</li> </ul> <p>個人的には後者の方が暗黙的な挙動が少なくて好きですが、前者の方が記述は楽です。<br /> この記事では、同じDOM構造を実現するための2種類の書き方を比較することで、 <code>Form.Input</code> を使った場合に行われることを理解します。</p> <h2 id="2種類の記述方法を比較する"><a href="#2%E7%A8%AE%E9%A1%9E%E3%81%AE%E8%A8%98%E8%BF%B0%E6%96%B9%E6%B3%95%E3%82%92%E6%AF%94%E8%BC%83%E3%81%99%E3%82%8B">2種類の記述方法を比較する</a></h2> <p>ここでは次の2つの記述方法でほぼ同じDOM構造を実現します。</p> <ol> <li><code>Form.Input</code> のみを使う</li> <li><code>Form.Field</code> の子要素に <code>Input</code> を含める</li> </ol> <p>ソースコードは次の通りです。</p> <pre><code class="js">import React, { useState } from "react"; import { Form, Input, Label } from "semantic-ui-react"; const FormExampleFieldError = () => { const [firstName, setFirstName] = useState(""); const handleChange = e => { setFirstName(e.target.value); }; const error = firstName === "" ? "First name is required" : undefined; return ( <Form> {/* 1. Form.Inputのみを使う */} <Form.Input type="text" name="firstName" id="input1-firstName" placeholder="First name" value={firstName} onChange={handleChange} label="First name" error={error} /> {/* 2. Form.Fieldの子要素にInputを含める */} <Form.Field error={!!error}> <label htmlFor="input2-firstName">First name</label> <Input type="text" name="firstName" id="input2-firstName" placeholder="First name" value={firstName} onChange={handleChange} aria-describedby={error && "input2-firstName-error-message"} aria-invalid={!!error} /> {error && ( <Label prompt pointing="above" id="input2-firstName-error-message" role="alert" aria-atomic > {error} </Label> )} </Form.Field> </Form> ); }; export default FormExampleFieldError; </code></pre> <h3 id="レンダリング結果: エラー無しの場合"><a href="#%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E7%B5%90%E6%9E%9C%3A+%E3%82%A8%E3%83%A9%E3%83%BC%E7%84%A1%E3%81%97%E3%81%AE%E5%A0%B4%E5%90%88">レンダリング結果: エラー無しの場合</a></h3> <p>エラーが無い場合、レンダリング結果は次のようになりました。同等のDOM構造になっていることがわかります。なお <code>aria-invalid="false"</code> はデフォルト値なので、属性が無いのと同じ意味です。</p> <pre><code class="html"><form class="ui form"> <!-- 1. Form.Inputのみを使う --> <div class="field"><label for="input1-firstName">First name</label> <div class="ui input"><input name="firstName" placeholder="First name" id="input1-firstName" type="text" value="a"></div> </div> <!-- 2. Form.Fieldの子要素にInputを含める --> <div class="field"><label for="input2-firstName">First name</label> <div class="ui input"><input name="firstName" id="input2-firstName" placeholder="First name" aria-invalid="false" type="text" value="a"></div> </div> </form> </code></pre> <h3 id="レンダリング結果: エラー有りの場合"><a href="#%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E7%B5%90%E6%9E%9C%3A+%E3%82%A8%E3%83%A9%E3%83%BC%E6%9C%89%E3%82%8A%E3%81%AE%E5%A0%B4%E5%90%88">レンダリング結果: エラー有りの場合</a></h3> <p>エラーが有る場合、レンダリング結果は次のようになりました。こちらも同等のDOM構造になっています。 <code>Form.Input</code> を使うと、 <code>aria-invalid</code> や <code>aria-describedby</code> などのアクセシビリティ関連の属性が自動で追加されていることがわかります。</p> <pre><code class="html"><form class="ui form"> <!-- 1. Form.Inputのみを使う --> <div class="error field"><label for="input1-firstName">First name</label> <div class="ui input"><input aria-describedby="input1-firstName-error-message" aria-invalid="true" name="firstName" placeholder="First name" id="input1-firstName" type="text" value=""></div> <div class="ui pointing above prompt label" id="input1-firstName-error-message" role="alert" aria-atomic="true"> First name is required</div> </div> <!-- 2. Form.Fieldの子要素にInputを含める --> <div class="error field"><label for="input2-firstName">First name</label> <div class="ui input"><input name="firstName" id="input2-firstName" placeholder="First name" aria-describedby="input2-firstName-error-message" aria-invalid="true" type="text" value=""></div> <div id="input2-firstName-error-message" role="alert" aria-atomic="true" class="ui pointing above prompt label"> First name is required</div> </div> </form> </code></pre> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>Semantic UI Reactでは、 <code>Form.Input</code> を使うことで、少ない記述で次の3要素を持つ入力欄を作成できます。</p> <ul> <li>項目名を表すlabel</li> <li>メインの入力欄であるInput</li> <li>バリデーションエラー用のLabel</li> </ul> <p>特にバリデーションエラーの表示は自前で記述するとそれなりに大変なので、積極的に使っていきたいです。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/Semantic-Org/Semantic-UI-React/blob/v0.88.2/src/collections/Form/FormField.js">Form.Fieldのソースコード</a></li> </ul> <h2 id="おまけ: 子要素にinputを含めた場合"><a href="#%E3%81%8A%E3%81%BE%E3%81%91%3A+%E5%AD%90%E8%A6%81%E7%B4%A0%E3%81%ABinput%E3%82%92%E5%90%AB%E3%82%81%E3%81%9F%E5%A0%B4%E5%90%88">おまけ: 子要素にinputを含めた場合</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://react.semantic-ui.com/collections/form/">リファレンス</a> に書かれているように、 <code>Form.Field</code> には <code>Input</code> の代わりに、HTMLの <code>input</code> 要素を含めることもできます。この場合、若干異なるDOM構造が生成されるものの、見た目は同じになりました。DOM構造の差異は、inputが <code><div class="ui input"></code> によって囲まれない点だけです。</p> <p>ソース</p> <pre><code class="js"> {/* 3. Form.Fieldの子要素にhtmlのinputを含める */} <Form.Field error={error}> <label htmlFor="input3-firstName">First name</label> <input type="text" name="firstName" id="input3-firstName" placeholder="First name" value={firstName} onChange={handleChange} aria-describedby={error && "input3-firstName-error-message"} aria-invalid={!!error} /> {error && ( <Label prompt pointing="above" id="input3-firstName-error-message" role="alert" aria-atomic > {error} </Label> )} </Form.Field> </code></pre> <p>レンダリング結果: エラー無しの場合</p> <pre><code class="html"> <!-- 3. Form.Fieldの子要素にhtmlのinputを含める --> <div class="field"><label for="input3-firstName">First name</label><input type="text" name="firstName" id="input3-firstName" placeholder="First name" aria-invalid="false" value="a"></div> </code></pre> <p>レンダリング結果: エラー有りの場合</p> <pre><code class="html"> <!-- 3. Form.Fieldの子要素にhtmlのinputを含める --> <div class="error field"><label for="input3-firstName">First name</label><input type="text" name="firstName" id="input3-firstName" placeholder="First name" aria-describedby="input3-firstName-error-message" aria-invalid="true" value=""> <div id="input3-firstName-error-message" role="alert" aria-atomic="true" class="ui pointing above prompt label"> First name is required</div> </div> </code></pre> <p>エラーの有無にかかわらず、見た目では区別がつきません。</p> <p><a href="https://crieit.now.sh/upload_images/5a11f6f3844392eef8bb653508a390ab5f01e4095de34.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5a11f6f3844392eef8bb653508a390ab5f01e4095de34.png?mw=700" alt="エラーが無い場合の見た目" /></a></p> <p><a href="https://crieit.now.sh/upload_images/5a11f6f3844392eef8bb653508a390ab5f01e43621baf.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5a11f6f3844392eef8bb653508a390ab5f01e43621baf.png?mw=700" alt="エラーが有る場合の見た目" /></a></p> <hr /> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>Material UIの <a target="_blank" rel="nofollow noopener" href="https://material-ui.com/components/text-fields/">TextField</a> と同じようなイメージです。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> かと tag:crieit.net,2005:PublicArticle/15821 2020-04-12T16:25:35+09:00 2020-04-12T16:25:35+09:00 https://crieit.net/posts/data-class-JSON 単一プロパティを持つdata classをJSONのプリミティブ型として表現する <p>Value Objectとして使っている、単一プロパティを持つdata classを、シンプルなJSONとして表現するのにちょっと手こずったのでまとめておきます。</p> <h2 id="問題設定"><a href="#%E5%95%8F%E9%A1%8C%E8%A8%AD%E5%AE%9A">問題設定</a></h2> <p>例として、次のようなクラス定義を考えます。</p> <pre><code class="kt">data class User(val userId: UserId, val userName: UserName) data class UserId(val value: Int) data class UserName(val value: String) </code></pre> <p>UserオブジェクトをJacksonでシリアライズした場合、普通だと次のようになります。</p> <pre><code class="json">{"userId": {"value": 1}, "userName": {"value": "sato"<span>}</span><span>}</span> </code></pre> <p>これを次のようにシリアライズした上で、元の形にデシリアライズしたいというのが、この記事の主題です。</p> <pre><code class="json">{"userId": 1, "userName": "sato"} </code></pre> <p>なお、 <code>UserId</code> や <code>UserName</code> は <code>User</code> 以外のクラスからも使われる可能性があります。これらを使うクラス間で挙動を統一できるよう、クラス定義に手を加えるなら <code>User</code> よりも <code>UserId</code> や <code>UserName</code> に加えたいです。</p> <h2 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h2> <p>次のように、 <code>UserId</code> や <code>UserName</code> にアノテーション <code>@JsonCreator</code> と <code>@JsonValue</code> をつけると実現できます。</p> <pre><code class="kt">data class User(val userId: UserId, val userName: UserName) data class UserId @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: Int) data class UserName @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: String) </code></pre> <h2 id="解説"><a href="#%E8%A7%A3%E8%AA%AC">解説</a></h2> <p>まず、 <a target="_blank" rel="nofollow noopener" href="http://fasterxml.github.io/jackson-annotations/javadoc/2.9/com/fasterxml/jackson/annotation/JsonValue.html">@JsonValue</a> はシリアライズに影響を与えるアノテーションで、シリアライズ結果を取得するメソッドに付けます。</p> <p>次に、 <a target="_blank" rel="nofollow noopener" href="http://fasterxml.github.io/jackson-annotations/javadoc/2.9/com/fasterxml/jackson/annotation/JsonCreator.html">@JsonCreator</a> はデシリアライズに影響を与えるアノテーションで、JSONの値からオブジェクトを作るためのコンストラクターやファクトリーメソッドに付けます。Javaの場合はコンストラクターに <code>@JsonCreator</code> をつけるだけでいいのですが、Kotlin Moduleを使っている場合はうまく行かないので、 <code>mode = JsonCreator.Mode.DELEGATING</code> が必要です [1]。</p> <p>なお、Kotlinでプライマリコンストラクターにアノテーションをつけるには、 <code>constructor</code> キーワードが必要です [2]。</p> <h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2> <p>実際のコードは次のようになります。</p> <p>build.gradleの一部</p> <pre><code class="gradle">dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "com.fasterxml.jackson.core:jackson-databind:2.9.10.4" implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.10" } </code></pre> <p>Main.kt</p> <pre><code class="kt">import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue fun main() { val u = User(UserId(1), UserName("sato")) println(u) val mapper = jacksonObjectMapper() val json = mapper.writeValueAsString(u) println(json) val parsed: User = mapper.readValue(json) println(parsed) } data class User(val userId: UserId, val userName: UserName) data class UserId @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: Int) data class UserName @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: String) </code></pre> <p>実行結果</p> <pre><code>User(userId=UserId(value=1), userName=UserName(value=sato)) {"userId":1,"userName":"sato"} User(userId=UserId(value=1), userName=UserName(value=sato)) </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li>java - How to instruct Jackson to serialize a field inside an Object instead of the Object it self? - Stack Overflow<br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/11031110/how-to-instruct-jackson-to-serialize-a-field-inside-an-object-instead-of-the-object-it-self">https://stackoverflow.com/questions/11031110/how-to-instruct-jackson-to-serialize-a-field-inside-an-object-instead-of-the-object-it-self</a></li> <li>java - Is there a generic way to deserialize single-valued value objects (without custom Deserializer) in Jackson - Stack Overflow<br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/55492657/is-there-a-generic-way-to-deserialize-single-valued-value-objects-without-custo">https://stackoverflow.com/questions/55492657/is-there-a-generic-way-to-deserialize-single-valued-value-objects-without-custo</a></li> </ul> <h2 id="注釈"><a href="#%E6%B3%A8%E9%87%88">注釈</a></h2> <ul> <li>[1] @jsonCreator on primary constructor is not working · Issue #305 · FasterXML/jackson-module-kotlin<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/FasterXML/jackson-module-kotlin/issues/305">https://github.com/FasterXML/jackson-module-kotlin/issues/305</a></li> <li>[2] https://kotlinlang.org/docs/reference/annotations.html#usage</li> </ul> かと