satococoa's blog

主にサーバーサイド、Web 系エンジニアのブログです。Go, Ruby, React, GCP, ...etc.

goでjsonの一部のデータだけを後から型を指定して取り出したい

例えばAPIなんかでたまにあるこう言う形。 これを受け取った場合、共通処理としてまずはerrorがあるかどうかを見て、もしerrorがなかった場合にはdataをそのAPIに合わせた型にして取り出したいとする。

{
  "error": null,
  "data": {
    "title": "fuga"
  }
}
{
  "error": null,
  "data": {
    "nickname": "foobar"
  }
}

json.RawMessage を使うと後からjsonの一部だけを型を指定して取り出すことができる。 まさに今回欲しかったものにぴったり。 golang.org

サンプルはこちら https://play.golang.org/p/e2lW0-15o3P

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

var userJSON = `{
  "error": null,
  "data": {
      "nickname": "hoge"
  }
}`
var todoJSON = `{
  "error": null,
  "data": {
      "title": "fuga"
  }
}`

type Response struct {
    Error *ResponseError  `json:"error,omitempty"`
    Data  json.RawMessage `json:"data,omitempty"` // <- User か Todo になる
}

type ResponseError struct {
    Code    int    `json:"code,omitempty"`
    Message string `json:"message,omitempty"`
}

type User struct {
    Nickname string `json:"nickname,omitempty"`
}

type Todo struct {
    Title string `json:"title,omitempty"`
}

func parseResponse(data []byte) (*Response, error) {
    res := &Response{}
    if err := json.Unmarshal(data, res); err != nil {
        return nil, err
    }
    if res.Error != nil {
        return nil, fmt.Errorf("error response: %v\n", res.Error)
    }
    return res, nil
}

func main() {
    // User APIにアクセスした
    userRes, err := parseResponse([]byte(userJSON))
    if err != nil {
        log.Fatalf("parse error: %v", err)
    }
    user := &User{}
    if err := json.Unmarshal(userRes.Data, user); err != nil {
        log.Fatalf("error: %v", err)
    }
    fmt.Printf("user: %+v\n", user)

    // TODO APIにアクセスした
    todoRes, err := parseResponse([]byte(todoJSON))
    if err != nil {
        log.Fatalf("parse error: %v", err)
    }
    todo := &Todo{}
    if err := json.Unmarshal(todoRes.Data, todo); err != nil {
        log.Fatalf("error: %v", err)
    }
    fmt.Printf("todo: %+v\n", todo)

}