Edited at

JSON Schema や JSON から Go の struct を生成する

More than 1 year has passed since last update.

golang には標準で JSON 用のパッケージがあって、Go で JSON を読み込んだり、書き出したりするときは、そのための struct を定義することがほとんどだと思います。

便利に使わせてもらっているのですが、 struct を定義していくのが結構面倒だったりして、これを自動で生成しようというソフトウェアがいくつかあります。

自分も作ったうちのひとりなのですが、皆さん考えることは同じですね……

機能やできることも異なるので、それぞれの特徴などを紹介していきます。


schematyper

JSON Schema :arrow_right: struct


作者

Igor Dubinskiy

ライセンス

MIT

JSON Schema の type に null がある要素はポインタで表現されます。(*stringなど)

JSON Schema で required でない要素は、 omitempty のタグが付きます。

JSON Schhema の description に改行文字を含む場合、 title が英数字でない場合など正常にソースコードを出力できなかったりします。

object の description の内容をコメントで出力します。

format が date-time のものは time.Time とします。(RFC3339であれば動作します)

type: ["string", "integer"] のように異なる型を持つ配列は interface{} として対応しています。


sample

//// $ curl -O "http://qiita.com/api/v2/schema"

//// $ jq '.properties.comment + {"type": "object"}' schema > comment.json
//// # 解析可能な JSON Schema とするため comment.json の .properties.user に {"type": "object"} を追加
//// # ファイル名とtitile から struct 名を決めるようなので `"title": "ユーザ"` を `"title": "user"` に書き換え
//// $ schematyper comment.json
package main

// generated by "schematyper comment.json" -- DO NOT EDIT

import "time"

// 投稿に付けられたコメントを表します。
type comment struct {
Body string `json:"body"`
CreatedAt time.Time `json:"created_at"`
ID string `json:"id"`
RenderedBody string `json:"rendered_body"`
UpdatedAt time.Time `json:"updated_at"`
User user `json:"user"`
}

// Qiita上のユーザを表します。
type user struct {
Description *string `json:"description"`
FacebookID *string `json:"facebook_id"`
FolloweesCount int `json:"followees_count"`
FollowersCount int `json:"followers_count"`
GithubLoginName *string `json:"github_login_name"`
ID string `json:"id"`
ItemsCount int `json:"items_count"`
LinkedinID *string `json:"linkedin_id"`
Location *string `json:"location"`
Name *string `json:"name"`
Organization *string `json:"organization"`
PermanentID int `json:"permanent_id"`
ProfileImageURL string `json:"profile_image_url"`
TwitterScreenName *string `json:"twitter_screen_name"`
WebsiteURL *string `json:"website_url"`
}


schematic

JSON Hyper-Schema :arrow_right: HTTP client


Generate Go client code for HTTP APIs described by JSON Hyper-Schemas.


と説明にあるように、 struct の生成というよりは client を生成する。

以下のブログにあるように Fork して改変して利用している例もあるようです。

JSON Schema から API 仕様と Go コードを自動で生成する – BOT エントリーの裏側 Part.1 | eureka tech blog


sample

//// $ curl -O "http://qiita.com/api/v2/schema"

//// $ jq '.properties.comment + {"type": "object"}' schema > comment.json
//// # 解析可能な JSON Schema とするため comment.json の .properties.user に {"type": "object"} を追加
//// # ファイル名とtitile から struct 名を決めるようなので `"title": "ユーザ"` を `"title": "user"` に書き換え、 `"title": "コメント"` を `"title": "comment"` に書き換え
//// $ schematic comment.json
// Generated service client for comment API.
//~~~
// Qiita上のユーザを表します。
type User struct {
Description *string `json:"description" url:"description,key"` // 自己紹介文
FacebookID *string `json:"facebook_id" url:"facebook_id,key"` // Facebook ID
FolloweesCount int `json:"followees_count" url:"followees_count,key"` // このユーザがフォローしているユーザの数
FollowersCount int `json:"followers_count" url:"followers_count,key"` // このユーザをフォローしているユーザの数
GithubLoginName *string `json:"github_login_name" url:"github_login_name,key"` // GitHub ID
ID string `json:"id" url:"id,key"` // ユーザID
ItemsCount int `json:"items_count" url:"items_count,key"` // このユーザが qiita.com 上で公開している投稿の数
// (Qiita:Teamでの投稿数は含まれません)
LinkedinID *string `json:"linkedin_id" url:"linkedin_id,key"` // LinkedIn ID
Location *string `json:"location" url:"location,key"` // 居住地
Name *string `json:"name" url:"name,key"` // 設定している名前
Organization *string `json:"organization" url:"organization,key"` // 所属している組織
PermanentID int `json:"permanent_id" url:"permanent_id,key"` // ユーザごとに割り当てられる整数のID
ProfileImageURL string `json:"profile_image_url" url:"profile_image_url,key"` // 設定しているプロフィール画像のURL
TwitterScreenName *string `json:"twitter_screen_name" url:"twitter_screen_name,key"` // Twitterのスクリーンネーム
WebsiteURL *string `json:"website_url" url:"website_url,key"` // 設定しているWebサイトのURL
}


structr

JSON Schema :arrow_right: struct

JSONSchemaからstructのようなコードを生成する"structr"というのを書いた

yaml で書かれた設定ファイルから変換の設定を変更できることが特徴。


sample

//// $ curl -O "http://qiita.com/api/v2/schema"

//// $ structr template > conf.yaml
//// $ jq '.properties.comment | .+ {"type": "object"}' schema > comment.json
//// $ structr generate -c conf.yaml comment.json
////2017/08/16 02:08:22 cannot add load json schema: json: cannot unmarshal array into Go struct field JsonSchema.type of type main.JsonSchemaType
///// すみません動きませんでした


schemarshal

JSON Schema :arrow_right: struct


作者

aaharu

ライセンス

BSD-2-Clause

筆者作 :writing_hand_tone2:

JSON Schema から "encoding/json" の Mashal に使える構造体を作ることから命名しました。

もっとカッコイイ名前のほうが良かったかなあと思ってたりもします :pensive:

以下に対応することを目的に作りました。

- go generate から扱いやすいように、パイプからの入力、引数での入出力に対応

- JSON Schema のパーサーと、コード生成のロジックを分ける

- enum 対応(MarshalJSON, UnmarshalJSON も生成)

JSON Schema の type に null がある要素はポインタで表現されます。(*stringなど)

JSON Schema で required でない要素は、 omitempty のタグが付きます。

format が date-time のものは time.Time とします。(RFC3339であれば動作します)

description の内容をコメントで出力しますが、オプションでコメント出力を切ることもできます。

type: ["string", "integer"] のように異なる型を持つ配列は未対応です。

date-time を time.Time にしたり、 number を float64 とする部分は設定で変更できないので、今後設定で変えられるような仕組みを検討中です。

要望があれば取り込んでいきたいとは思っています。


sample

//// $ curl -s "http://qiita.com/api/v2/schema" | schemarshal

// Code generated by schemarshal 1.2.0 `schemarshal`
// DO NOT RECOMMEND EDITING THIS FILE.

package main
//~~~
// QiitaAPIV2JSONSchemaCommentUserObject : Qiita上のユーザを表します。
type QiitaAPIV2JSONSchemaCommentUserObject struct {
// Description : 自己紹介文
Description *string `json:"description"`
// FacebookID : Facebook ID
FacebookID *string `json:"facebook_id"`
// FolloweesCount : このユーザがフォローしているユーザの数
FolloweesCount int64 `json:"followees_count"`
// FollowersCount : このユーザをフォローしているユーザの数
FollowersCount int64 `json:"followers_count"`
// GithubLoginName : GitHub ID
GithubLoginName *string `json:"github_login_name"`
// ID : ユーザID
ID string `json:"id"`
// ItemsCount : このユーザが qiita.com 上で公開している投稿の数 (Qiita:Teamでの投稿数は含まれません)
ItemsCount int64 `json:"items_count"`
// LinkedinID : LinkedIn ID
LinkedinID *string `json:"linkedin_id"`
// Location : 居住地
Location *string `json:"location"`
// Name : 設定している名前
Name *string `json:"name"`
// Organization : 所属している組織
Organization *string `json:"organization"`
// PermanentID : ユーザごとに割り当てられる整数のID
PermanentID int64 `json:"permanent_id"`
// ProfileImageURL : 設定しているプロフィール画像のURL
ProfileImageURL string `json:"profile_image_url"`
// TwitterScreenName : Twitterのスクリーンネーム
TwitterScreenName *string `json:"twitter_screen_name"`
// WebsiteURL : 設定しているWebサイトのURL
WebsiteURL *string `json:"website_url"`
}

// QiitaAPIV2JSONSchemaCommentObject : 投稿に付けられたコメントを表します。
type QiitaAPIV2JSONSchemaCommentObject struct {
// Body : コメントの内容を表すMarkdown形式の文字列
Body string `json:"body"`
// CreatedAt : データが作成された日時
CreatedAt time.Time `json:"created_at"`
// ID : コメントの一意なID
ID string `json:"id"`
// RenderedBody : コメントの内容を表すHTML形式の文字列
RenderedBody string `json:"rendered_body"`
// UpdatedAt : データが最後に更新された日時
UpdatedAt time.Time `json:"updated_at"`
// User : Qiita上のユーザを表します。
User QiitaAPIV2JSONSchemaCommentUserObject `json:"user"`
}
//~~~


gojson

JSON :arrow_right: struct


作者

ChimeraCoder

ライセンス

GPL-3.0

紹介した他のものとは異なり、JSON Schemaからではなく、既存のJSONから生成するのが特徴。

そのため、既に公開済み、実装済みのAPIレスポンスから定義を生成するのに向いていると思います。

gojson コマンドは、パイプからの入力、引数での入出力に対応し、 go generate にも組み込みやすい作りです :thumbsup_tone2:

nest した struct というのでしょうか、、結果は1つの大きな struct を生成します。

null を含むデータから生成しましたが、 string として判定されました。


sample

//// $ curl -s "http://qiita.com/api/v2/items/c686397e4a0f4f11683d/comments" | gojson

package main

type Foo []struct {
Body string `json:"body"`
CreatedAt string `json:"created_at"`
ID string `json:"id"`
RenderedBody string `json:"rendered_body"`
UpdatedAt string `json:"updated_at"`
User struct {
Description string `json:"description"`
FacebookID string `json:"facebook_id"`
FolloweesCount int64 `json:"followees_count"`
FollowersCount int64 `json:"followers_count"`
GithubLoginName string `json:"github_login_name"`
ID string `json:"id"`
ItemsCount int64 `json:"items_count"`
LinkedinID string `json:"linkedin_id"`
Location string `json:"location"`
Name string `json:"name"`
Organization string `json:"organization"`
PermanentID int64 `json:"permanent_id"`
ProfileImageURL string `json:"profile_image_url"`
TwitterScreenName string `json:"twitter_screen_name"`
WebsiteURL string `json:"website_url"`
} `json:"user"`
}


JSON-to-Go

JSON :arrow_right: struct

go generate での使用を想定した他のものとは異なり、 JavaScript で書かれていてブラウザで動作します。

やっていることは gojson のブラウザ版という感じでしょうか。

null を含むデータは、 interface{} として判定されました。


sample

//// $ curl -s "http://qiita.com/api/v2/items/c686397e4a0f4f11683d/comments"

//// の結果を JSON-to-Go に貼り付けました。
type AutoGenerated []struct {
Body string `json:"body"`
CreatedAt time.Time `json:"created_at"`
ID string `json:"id"`
RenderedBody string `json:"rendered_body"`
UpdatedAt time.Time `json:"updated_at"`
User struct {
Description interface{} `json:"description"`
FacebookID interface{} `json:"facebook_id"`
FolloweesCount int `json:"followees_count"`
FollowersCount int `json:"followers_count"`
GithubLoginName string `json:"github_login_name"`
ID string `json:"id"`
ItemsCount int `json:"items_count"`
LinkedinID interface{} `json:"linkedin_id"`
Location interface{} `json:"location"`
Name string `json:"name"`
Organization interface{} `json:"organization"`
PermanentID int `json:"permanent_id"`
ProfileImageURL string `json:"profile_image_url"`
TwitterScreenName string `json:"twitter_screen_name"`
WebsiteURL interface{} `json:"website_url"`
} `json:"user"`
}


まとめ

struct を生成する、ということに注目すると、 schematyper, schemarshal, gojson, JSON-to-Go あたりが向いています。

schematyper と schemarshal はほぼ同等の機能を持っていて、 enum や異なる型を持つ配列あたりの対応に差があります。

既にある API のレスポンスをパースしたいというだけなら JSON-to-Go を使うのが一番楽だと思います。

ただしその API がクエリなどによってレスポンスに含まれる要素が異なったりする場合、 結果に手を入れたりすることなどが必要になってきます。( omitempty の判断などができない)

go generate で実行したい場合などは gojson を使うのも良いかもしれません。

API 提供側で JSON Schema が用意してあったり、 API と JSON Schema を新規作成する場合などは、 schematyper か schemarshal を使用すると楽ができるんじゃないかと思います。