LoginSignup
68
63

More than 5 years have passed since last update.

Go言語とJSON

Last updated at Posted at 2015-01-16
{
  "Id": 1
}

誰がこんなもん欲しがるって言うんだよォー!!
クライアント側(JavaScriptとかだ)が明らかに困るだろ!

現状の確認

素直なコードとJSON

先のJSONは以下のようなコードで生成した。

package main

import (
    "encoding/json"
    "fmt"
)

type Sample struct {
    Id int
}

func main() {
    data := Sample{1}
    bytes, err := json.Marshal(data)
    if err != nil {
        return
    }
    fmt.Println(string(bytes))
}

Go言語がわからない人でもきっとなんとなく読めるだろう。
素直だ。わかりやすい。このコードが"Id"ではなく"id"というkeyでJSON生成してくれれば何も問題はなかったのに。

少し書くのがめんどいコードとJSON

もちろん、出力されるkeyの名前を変えるくらい、わけない。
こうすればいいんだ。

type Sample struct {
    Id int `json:"id"`
}

実に簡単。タグというメタデータを書く仕組みに則って書くだけだ。
もちろん、structのプロパティ全てに対して書く必要がある。
json.Marshal(data, &json.Option{LowerCamel:true}みたいな書き方があるはずだ…!って?
僕の調べた限り、ない。
頑張って全てに書くんだ。
フィールド名変えたらタグの中身も変えるんだ。
いちいちやるんだ。全部やるんだ。

標準ライブラリ以外のJSONライブラリは?

僕がざっくり調べた限り、広く使われているものはないみたいだ。
色々なフレームワークが標準ライブラリのencoding/jsonで動作することを期待している。
逃れることはできない…!

json.Marshaler

encoding/jsonでは、Marshalerというインタフェースを用意していて、そのメソッドを実装すれば挙動を自分で上書きできる。こんな感じ(ちなUnmarshalerもある)

package main

import (
    "encoding/json"
    "fmt"
)

type Sample struct {
    Id int
}

func (s *Sample) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf("{\"id\":%d}", s.Id)), nil
}

func main() {
    data := &Sample{1}
    bytes, err := json.Marshal(data)
    if err != nil {
        return
    }
    fmt.Println(string(bytes))
}

なので、自分でMarshalerを書くという逃げ道もあるだろう。
なお、jsonのstream encoder/decoder的なものは標準ライブラリには存在していない。
encoding/json内部には持っているのだが、非公開なのだ。
JSR353みたいなの欲しい。

現実的な対策

泣きながら頑張ってタグ書く

たぶんみんなこうやってるんだろうな

json.Marshalerで頑張る

汎用的な変換ロジックを1つこさえて、JSONに変換する可能性があるたび、メソッド(的なの)を定義するのだ。
でもタグ情報が足りなくて上手く動かないコードありそう。

go generateを使う

いい感じのコードを生成するツールを作ればいんじゃね?
AST触りやすいGoさん偉いと思います go fmt もうまあじ

package main

import (
    "encoding/json"
    "fmt"
)

type Sample struct {
    Id int
}

func main() {
    data := &Sample{1}
    bytes, err := json.Marshal(NewSampleJson(data))
    if err != nil {
        return
    }
    fmt.Println(string(bytes))
}

// ここから自動生成

func NewSampleJson(orig *Sample) *SampleJson {
    ret := &SampleJson{}
    ret.Id = orig.Id
    return ret
}

type SampleJson struct {
    Id int `json:"id"`
}

func (s *SampleJson) MarshalJSON() ([]byte, error) {
    return []byte(fmt.Sprintf("{\"id\":%d}", s.Id)), nil
}

フィールドのコピーを行うのが地味にダサい気がするんだけどどうなんだろう…。
あとは、ユーザが管理者か一般ユーザか非ログインかで出力するフィールドの制御とかも欲しいよね…。
以下みたいな感じで…。

type Person struct {
    Id string
    Name string
    Email string
    Himitsu string
}

func main() {
    p := &Person{"vv", "vvakame", "vvakame@example.com", "猫が好き"}
    b := NewSampleJsonBuilder()
    b.AddAll()
    b.Remove(b.Himitsu)
    bytes, err := b.Marshal(p)
}

だいたいJsonPullParserだな!

実装例
この例だとomitemptyで""とかも要素消えちゃうのでnilにしたい気がするけど*stringはなんか日常生活がダルそうだからなぁ…みたいな気持ち。そんなことない?

お願い

まだGo言語歴20時間くらいなので、なんかナイスな方法とか意見とかあったらコメントください。
お願いします!なんでもはしません!


追記 2015/06/25
jwgというライブラリを作って公開しました。

68
63
2

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
68
63