Rubyと筋肉とギターとわたし

筋トレが仕事です

【Golang】mapを構造体へ割当(変換)する

どうもてぃ。

前回のつづきで備忘録。

smot93516.hatenablog.jp

mapと構造体を準備

type Addr struct {
    PostalCode int
    Country    string
}

type User struct {
    Name    string
    Age     int
    Address Addr
    Email   string
}

user := map[string]interface{}{
    "Name": "motty",
    "Age":  30,
    "Address": map[string]interface{}{
        "PostalCode": 1234567,
        "Country":    "japan",
    },
    "Email": "test@gmail.com",
}

構造体で定義した型に合わせてmapを作成してます。

map側の型と構造体の型が違った場合どうなるか…というのは後ほどやっていきます。

mapをjsonへ変換

encoding/jsonjson.Marshalを使用。

// mapをjsonへ変換
byte, err := json.Marshal(user)
if err != nil {
    fmt.Println(err)
    return
}
fmt.Println(string(byte))

こんな感じ。string(byte)の結果は以下。

{"Address":{"Country":"japan","PostalCode":1234567},"Age":30,"Email":"test@gmail.com","Name":"motty"}

json.Marshalの定義元を確認すると、

func Marshal(v interface{}) ([]byte, error) {
    e := newEncodeState()

    err := e.marshal(v, encOpts{escapeHTML: true})
    if err != nil {
        return nil, err
    }
    buf := append([]byte(nil), e.Bytes()...)

    encodeStatePool.Put(e)

    return buf, nil
}

と、最終的には[]byteerrorが返ってるので

byte, err := json.Marshal(user)

のようにerrも受け取って上げる必要がある。

とりあえず検証するだけ、エラーハンドリング不要であれば

byte, _ := json.Marshal(user)

として省くことも出来きます。

jsonを構造体へ

encoding/jsonjson.Unmarshalを使用。

// jsonを構造体へ割当
var u User
if err := json.Unmarshal(byte, &u); err != nil {
    fmt.Println(err)
    return
}

fmt.Println(u)

json.Unmarshalの定義元を確認。

func Unmarshal(data []byte, v interface{}) error {
    // Check for well-formedness.
    // Avoids filling out half a data structure
    // before discovering a JSON syntax error.
    var d decodeState
    err := checkValid(data, &d.scan)
    if err != nil {
        return err
    }

    d.init(data)
    return d.unmarshal(v)
}

Unmarshalの場合errorのみ返ってきて、第二引数に渡したポインタアドレスに第一引数で渡した値がスキャニングされてるっぽい(辿っていったけど詳しくはわからん)。

全部合わせたやつ

package main

import (
    "encoding/json"
    "fmt"
)

type Addr struct {
    PostalCode int
    Country    string
}

type User struct {
    Name    string
    Age     int
    Address Addr
    Email   string
}

func main() {
    user := map[string]interface{}{
        "Name": "motty",
        "Age":  30,
        "Address": map[string]interface{}{
            "PostalCode": 1234567,
            "Country":    "japan",
        },
        "Email": "test@gmail.com",
    }

    // mapをjsonへ変換
    byte, err := json.Marshal(user)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(byte))

    // jsonをstructへ割当
    var u User
    if err := json.Unmarshal(byte, &u); err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(u)
}

実行結果

{"Address":{"Country":"japan","PostalCode":1234567},"Age":30,"Email":"test@gmail.com","Name":"motty"}
map[Address:map[Country:japan PostalCode:1234567] Age:30 Email:test@gmail.com Name:motty]

これをある程度理解していれば、簡単なwebサーバーは作れる。

サーバーを作るのはまた次回にするとして…

mapの型と構造体の型が異なる場合

たとえば以下のように構造体UserのAgeがint型であるにもかかわらず、map側がstringになっていた場合。

type Addr struct {
    PostalCode int
    Country    string
}

type User struct {
    Name    string
    Age     int
    Address Addr
    Email   string
}

user := map[string]interface{}{
    "Name": "motty",
    "Age":  "30",
    "Address": map[string]interface{}{
        "PostalCode": 1234567,
        "Country":    "japan",
    },
    "Email": "test@gmail.com",
}

何が起きるか、実行してみると

{"Address":{"Country":"japan","PostalCode":1234567},"Age":"30","Email":"test@gmail.com","Name":"motty"}
json: cannot unmarshal string into Go struct field User.Age of type int

jsonを構造体へ割当の部分、つまりjson.Unmarshal部分でエラーが返ってきます(しっかりしてんな)。

やってくるリクエスト(map)に対して構造体をしっかり用意してあげないと、今回のようにエラーが返ってくる…といった感じです。

mapと構造体でkey, valueの数が異なる場合

構造体で用意してある型とmapの型は一致しているが、mapの中身の要素数が異なる時どうなるか。

例えば、mapに"Test": "test"というkey, valueが入っていたとき

type Addr struct {
    PostalCode int
    Country    string
}

type User struct {
    Name    string
    Age     int
    Address Addr
    Email   string
}

user := map[string]interface{}{
    "Name": "motty",
    "Age":  30,
    "Address": map[string]interface{}{
        "PostalCode": 1234567,
        "Country":    "japan",
    },
    "Email": "test@gmail.com",
    "Test": "test",
}

こういう場合、最終的にはエラーは出ず構造体側が優先されます。

json.Unmarshalされたとき、構造体にないものは割り当てられないという認識でいると良いです。

もし"Test"を受け取りたいなら、構造体側にちゃんと型を定義してあげましょう。

最後に

Goは楽しいですよ。

次回は構造体→mapを書こうと思います。