Help us understand the problem. What is going on with this article?

[Go] JSONの中身に応じて違うstructにデコードする

2020-05-10 追記

中身が不定のJSONオブジェクトをGo言語で扱うのに mattn/go-jsonpointer が便利だった - えいのうにっき によると今なら mattn/go-jsonpointer が良さそうです。

元の文

まずgoでJSONを扱う基本はJSON and Go - The Go Programming Languageを参照してください。

mattyw: Using go to unmarshal json lists with multiple typesのブログ記事で紹介されていた内容が興味深かったので共有します。

このブログ記事は配列の要素に2つのデータタイプPerson, Placeが混在していて、それぞれJSONのキー名が異なる場合にどうデコードするかという話です。ブログのコメントにもあるように、APIの仕様を変えられるならキーを分けて配列の要素は混在しないようにしたほうがよいという話はあります。が、この例以外にもJSONの中身に応じてgo側では違う型にマッピングしたいケースはあると思いますので参考になります。

4つの実装が紹介されています。

  • 一旦mapにデコードして、そこからstructのインスタンスを作る
  • 2つのデータタイプのキーを両方持つstructを作ってそこにデコードしてから、最終的なstructのインスタンスを作る
  • 一旦json.RawMessageにデコードして、そこからstructのインスタンスを作る。
    • RawMessageは[]byteとして定義されているためこのキーを持っているかとかの判定は出来ません。そこでPersonとPlaceにそれぞれデコードしてみて、値が入った方を採用するということをしています。
  • dustin/go-jsonpointerというライブラリを使う。
// tlehman's solution using dustin's go-jsonpointer
func solutionD(jsonStr []byte) ([]Person, []Place) {
    persons := []Person{}
    places := []Place{}

    for i := int64(0); true; i++ {
        // build path to element of json array (e.g.   /things/2  )
        pathRoot := string(strconv.AppendInt([]byte("/things/"), i, 10))
        pathName := pathRoot + "/name"
        pathAge := pathRoot + "/age"
        pathCity := pathRoot + "/city"
        pathCountry := pathRoot + "/country"

        name, _ := jsonpointer.Find(jsonStr, pathName)
        city, _ := jsonpointer.Find(jsonStr, pathCity)

        if name != nil {
            age, _ := jsonpointer.Find(jsonStr, pathAge)
            agef, _ := strconv.ParseFloat(strings.TrimSpace(string(age)), 64)
            nameTrimmed := string(trimJsonBytes(name))
            persons = append(persons, Person{nameTrimmed, agef})

        } else if city != nil {
            country, _ := jsonpointer.Find(jsonStr, pathCountry)
            cityTrimmed := string(trimJsonBytes(city))
            countryTrimmed := string(trimJsonBytes(country))
            places = append(places, Place{cityTrimmed, countryTrimmed})

        } else {
            break
        }
    }
    return persons, places
}

go-jsonpointerの実装について

dustin/go-jsonpointerの実装は、goのJSON標準ライブラリのscanner.goに含まれる非公開API(先頭が小文字のscanner)を公開API(先頭が大文字のScanner)にしたdustin/gojsonに依存しています。

このScannerを使ってユーザにJSONポインタの文字列で指定されたキーまで進み、そこで必要な部分だけの値を取得できるという仕組みです。

標準のjson.Unmarshal()ではJSON全体に対応するgoのデータ構造を構築しますが、これだと関心がある部分についてのみデータ構造を作るので効率がいいというわけです。

sakura_internet
さくらレンタルサーバ、さくらのVPS、 さくらのクラウド、さくらの専用サーバなどのインターネットサービス・ITプラットフォームを提供しています。
https://www.sakura.ad.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした