4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SnakeYAMLによるYAMLデータのパース (なろうリーダー(仮) 3日目)

Last updated at Posted at 2018-11-01

やりたいこと

  • 【大目標】 小説家になろうの小説ダウンロードAndroidアプリの作成。
  • 【中目標】なろう小説APIを使った小説検索機能の実装。
  • 【この記事のゴール】なろう小説APIで取得したYamlデータのパース方法の模索。

SnakeYAMLについて

  • InputstreamやStringからYAMLデータを読み込み、適当な型にパースしてくれる便利ライブラリ。
  • YAMLデータにおいてアイテムが複数階層で定義される場合、戻り値は入れ子構造となる。
  • YAMLデータがおいてアイテムが並列に定義される場合、戻り値はListまたはArrayとなる。
  • load時に型を指定しない場合、YAML文章のKeyValueそれぞれに型推測を行いLinkedHashMap<Object,Object>を返す。
  • load時に型を指定した場合、指定した型にキャストして返してくれる。
    • 指定する型にはSetterとGetterの両方が定義されたデータを定義する必要がある。

型指定なしの場合

LinkedHashMap<String,Object> doc = yaml.load(inputStream);
YAML文章 生成されるJavaオブジェクト 備考
novel_type: 1 LinkedHashMap<String, Integer>
keyword: ほのぼの 学園 LinkedHashMap<String, String> 空白や改行が含まれる場合もStringは1つ
updated_at: 2018-10-30 05:53:44 LinkedHashMap<String, Date>
gensaku: null LinkedHashMap<String, Object> valueはnull

型指定ありの場合

Item doc = yaml.loadAs(inputStream, Item.class);
Item.java
public class Item {//Lombokを利用
  @Setter @Getter private Integer novel_type;
  @Setter @Getter private String keyword;
  @Setter @Getter private Date updated_at;
  @Setter @Getter private String gensaku;
}
  • 入力元のYAML構文に定義済み項目が存在しない場合、プリミティブ型の場合は例外(YamlException)、クラスの場合はnullとなる。

パースしたいYAML構文(なろう小説API出力)

---
-
 allcount 2
-
 title AAA
 writer aaa
 ...(その他多くのパラメータ)
-
 title BBB
 writer bbb
 ...(その他多くのパラメータ)
  • 1アイテム目に取得した小説数(allcount)を定義される。
  • 2アイテム以降が、実際の小説サマリデータとなる。(アイテム数はallcountに依存)
  • 入力元のYAML構文はできるだけ加工したくない。

パース後のデータ構造

こんなデータクラスに格納したい.java
public class NovelSummary { // allcount(小説数)は含まない
    @Setter @Getter private String title; 
    @Setter @Getter private String writer;
  …その他たくさん
} 

パースするのに困ること

  • トップレベルのアイテムが複数のため、本Yaml構文はArrayまたはList型でのみパースできる。
  • ジェネリクスでリストの型指定はできず、戻り値はLinkedHashMapとなる。
  • そもそも、1アイテム目(小説数)と2アイテム目以降(小説データ)が並列関係で定義されているため、一括でデータ化できない。
できません.java
List<NovelSummary> docs = yaml.load(target);

他の人はどうしてるの?

  • StackOverrun - 入力元のYAML文を改造(ルートにアイテムを追加し、当該アイテムのコンストラクタでパース)
  • baeldung - 同上

悩んだ結果、最終的な実装

  public static ArrayList<NovelSummary> parseYaml(String input) {
    ArrayList<NovelSummary> results = new ArrayList<>();
    Yaml yaml = new Yaml();

    List list = yaml.load(target); //戻り値は List<LinkedHashMap<String,?>>
    ListIterator iterator = list.listIterator(1); //最初の1アイテム目は小説数のため読み飛ばす
    while (iterator.hasNext()) {
      String docYaml = yaml.dump(iterator.next()); //LinkedHashMap から YAML構文を生成
      NovelSummary sr = yaml.loadAs(docYaml, NovelSummary.class); //YAML構文を独自データ型にキャスト
      results.add(sr);
    }
  return results;
  }

- 型指定なしでリスト形式にYAMLパースを実施し、リスト内の各要素に対して再度YAMLパースすることで独自データ型にキャストしてみる。
- SnakeYAMLにキャストさせずに、自分でHashMapから独自データ型を生成するほうがベーシックなような

まとめ

  • インテリジェンスに解析してくれるため、YAMLはとても便利。
  • ただし、データ構造が単純な場合に限る。
  • データ構造が複雑(ネストとか、並列階層のアイテムの構造が違うとか)の場合は、つらかとです。
4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?