かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

色々なプログラミング言語で JSON をパースするためのイカしたサービス quicktype

今日、マイクロソフト本社から来た David さんとお昼ご飯をしたときに教えてもらえた彼のプロダクトが凄かったので紹介したいと思います。

quicktype.io

quicktype

JSON をペーストするとシリアライズ・デシリアライズするためのコードを出力してくれるサービスです。

例えば、以下のような JSON があるとします。

{
  "name": "Kazuki Ota",
  "age": 36,
  "pets": [
    { "name": "hoge" },
    { "name": "foo" }
  ]
}

ページに張り付けるとこういうコードが生成されます。

// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
//    using Sample.Models;
//
//    var data = Person.FromJson(jsonString);
//
namespace Sample.Models
{
    using System;
    using System.Net;
    using System.Collections.Generic;

    using Newtonsoft.Json;

    public partial class Person
    {
        [JsonProperty("age")]
        public long Age { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("pets")]
        public Pet[] Pets { get; set; }
    }

    public partial class Pet
    {
        [JsonProperty("name")]
        public string Name { get; set; }
    }

    public partial class Person
    {
        public static Person FromJson(string json) => JsonConvert.DeserializeObject<Person>(json, Converter.Settings);
    }

    public static class Serialize
    {
        public static string ToJson(this Person self) => JsonConvert.SerializeObject(self, Converter.Settings);
    }

    public class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
        };
    }
}

画面はこんな感じです。使い方に迷うことはないと思われる。

f:id:okazuki:20171122145434p:plain

Visual Studio にも組み込みで JSON からコードを生成する機能があるのですが、ルートのクラス名が決めれなかったり、プロパティ名が JSON のプロパティ名と同じになるので小文字から始まるプロパティになったりしてもんにょりするのですが、そこらへんがきっちり考えられてる!!

今回は、C# の出力コード例を示しましたが他の言語にも対応しています。

  • Swift
  • TypeScript
  • Go
  • C#
  • Java
  • etc...

f:id:okazuki:20171122145710p:plain

例えば TypeScript だとこういう感じになります。

// To parse this data:
//
//   import { Convert, Person } from "./file";
//
//   const person = Convert.toPerson(json);

export interface Person {
    age:  number;
    name: string;
    pets: Pet[]; 
}

export interface Pet {
    name: string;
}

// Converts JSON strings to/from your types
export module Convert {
    export function toPerson(json: string): Person {
        return JSON.parse(json);
    }

    export function personToJson(value: Person): string {
        return JSON.stringify(value, null, 2);
    }
}

quicktype は本当に賢くて、以下のように複数の型が入り混じった配列の JSON を渡すと…

{
  "items": [ "a", "b", 3 ]
}

ちゃんと型をいい感じにしてくれます。

// To parse this data:
//
//   import { Convert, Person } from "./file";
//
//   const person = Convert.toPerson(json);

export interface Person {
    items: Array<number | string>;
}

// Converts JSON strings to/from your types
export module Convert {
    export function toPerson(json: string): Person {
        return JSON.parse(json);
    }

    export function personToJson(value: Person): string {
        return JSON.stringify(value, null, 2);
    }
}

このパワーをローカルで

サイトの下の方に気になる1文が書いてあります。

npm i -g quicktype

ということで nodejs は導入済みなので、ターミナルを開いてうってみました。

C:\Users\xxxxx>npm i -g quicktype
C:\Users\xxxxx\AppData\Roaming\npm\quicktype -> C:\Users\xxxxx\AppData\Roaming\npm\node_modules\quicktype\quicktype.js
+ quicktype@3.3.0
updated 1 package in 2.711s

入った!quicktype と打ち込むと使い方が出てきます。共通のオプションと C# の部分だけ抜粋してみました。

Synopsis

  $ quicktype [--lang cs|go|c++|java|ts|swift|elm|schema|types] FILE|URL ...

Description

  Given JSON sample data, quicktype outputs code for working with that data in
  C#, Go, C++, Java, TypeScript, Swift, Elm, JSON Schema, Simple Types.

Options

  -o, --out FILE                                        The output file. Determines --lang and --top-level.
  -t, --top-level NAME                                  The name for the top level type.
  -l, --lang cs|go|c++|java|ts|swift|elm|schema|types   The target language.
  -s, --src-lang json|schema                            The source language (default is json).
  --src FILE|URL|DIRECTORY                              The file, url, or data directory to type.
  --src-urls FILE                                       Tracery grammar describing URLs to crawl.
  --no-combine-classes                                  Don't combine similar classes.
  --no-maps                                             Don't infer maps, always use classes.
  --quiet                                               Don't show issues in the generated code.
  -h, --help                                            Get some help.

Options for C#

  --namespace NAME                                 Generated namespace
  --csharp-version 6|5                             C# version
  --density normal|dense                           Property density
  --array-type array|list                          Use T[] or List<T>
  --features complete|attributes-only|just-types   Output features

なんとなくわかる。コマンドから動けばこっちのもんですよね。例えば Visual Studio 2017 で適当なプロジェクトを作ります。

そして、適当な JSON を準備します。ここら辺のを使ってみようかな。Swagger(Open API) のファイルっぽい。

github.com

プロジェクトの下に datasources フォルダを作って petstore.url というファイルを作って上記サイトの JSON の Raw データへの URL をぺたっと貼ります。プロジェクトはこんな感じになりました。

f:id:okazuki:20171122153156p:plain

プロジェクトのプロパティのビルド前イベントのコマンドラインに以下のようなコマンドを書いておきます。単純に qicktype コマンドを書いてるだけですね。フォルダを指定するだけで、そこにある json や url ファイルを対象にしてくれます。

quicktype $(ProjectDir)datasources -o $(ProjectDir)Entities\Petstore.cs --namespace QuickTypeDemo.Entities --csharp-version 6 --features complete

ビルドして、プロジェクトですべてのファイルを表示すると確かに出来上がってることが確認できます。

f:id:okazuki:20171122153430p:plain

プロジェクトに追加しておきましょう。こんなにたくさん(まだ下に続いてます)クラスを生成してくれました。

f:id:okazuki:20171122153547p:plain

JSON.NET を参照に追加して、以下のようなコードを書いてみました。

using QuickTypeDemo.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace QuickTypeDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var json = new HttpClient().GetStringAsync("https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/petstore.json").Result;
            var petstore = Petstore.FromJson(json);
            Console.WriteLine(petstore.Info.Title);
        }
    }
}

ブレークポイントで止めてみたら、ちゃんと読み込めてることがわかりますね。

f:id:okazuki:20171122154048p:plain

他にも

Xcode と VSCode で使う方法は公式の Blog に記事があります。

blog.quicktype.io

さらに、今回はコマンドをビルドアクションで叩く感じでやりましたが、IDE などのツールとのインテグレーションは考えられてるみたいです。素敵!!

blog.quicktype.io

IDE/editor integration

まとめ

JSON を格納するための型定義にお悩みの人は試してみるといいと思いました!凄い!賢い! 個人的に、今まで触ってきた JSON をパースしてクラス定義を出力してくれるほかのサービスと比べて断然使いやすいと思いました。ローカルコマンドが提供されてるのもあついですね。

IDE やエディタとの連携が本当にシームレスになるとやばいことになりそうな気がします。

quicktype.io