Go言語での any 型データの型アサーションによる復元方法:ユーザーリストのパースを例に
はじめに
Go 1.18 以降で導入された any
型は、柔軟なデータ型として広く使われるようになりました。しかし、any
型としてデータを扱う場合、元の具体的な型に戻すための処理が必要になることがあります。本記事では、JSON データのアンマーシャル後に any
型のデータを元の struct
型に復元する方法を、「ユーザーリストのパース」を例に解説します。
サンプルコードの概要
このサンプルでは、ユーザー情報を格納した []User
型のデータを Dict
構造体にまとめて JSON マーシャルし、さらにアンマーシャルしてから元の型に戻す流れを実装しています。
構造体の定義
まず、ユーザー情報を持つ User
構造体と、メタ情報およびデータを含む汎用的な Dict
構造体を用意します。
type User struct {
Name string
Age int
}
type DictMeta struct {
Count int
Description string
}
type Dict struct {
Meta DictMeta
Data any
}
Dict
構造体の Data
フィールドは any
型として定義されているため、柔軟にデータを格納できますが、アンマーシャル後に元の []User
型に戻す際には工夫が必要です。
JSON のマーシャルとアンマーシャル
Dict
構造体に []User
型のデータを格納し、JSON 形式にマーシャルしてからアンマーシャルする手順は以下の通りです。
marshaledDict, err := json.Marshal(dict)
var unmarshaledDict Dict
if err = json.Unmarshal(marshaledDict, &unmarshaledDict); err != nil {
panic(err)
}
any
型データを元の型に戻すための関数
ここで、アンマーシャル後の any
型データを []User
型に戻す parseUnmarshaledDict
関数を実装します。この関数では、以下の手順でデータを復元します。
-
any
型として格納されているデータをスライス型[]any
にアサーションします。 - 各要素を
map[string]any
型として取り出し、User
構造体のフィールドに対応する値にアサーションします。 - それぞれの要素を
User
型のスライスに追加し、最終的にDict
構造体のData
フィールドに格納します。
以下が、parseUnmarshaledDict
関数の実装です。
func parseUnmarshaledDict(d Dict) (Dict, error) {
retDict := Dict{}
retDict.Meta = d.Meta
users := []User{}
anySlice, ok := d.Data.([]any)
if !ok {
return retDict, errors.New("failed to parse data")
}
for _, v := range anySlice {
userMap, ok := v.(map[string]any)
if !ok {
return retDict, errors.New("failed to parse user")
}
name, ok := userMap["Name"].(string)
if !ok {
return retDict, errors.New("failed to parse name")
}
age, ok := userMap["Age"].(float64)
if !ok {
return retDict, errors.New("failed to parse age")
}
user := User{Name: name, Age: int(age)}
users = append(users, user)
}
retDict.Data = users
return retDict, nil
}
実行結果の確認
実行するための main.go
package main
import (
"encoding/json"
"errors"
"fmt"
)
type User struct {
Name string
Age int
}
type DictMeta struct {
Count int
Description string
}
type Dict struct {
Meta DictMeta
Data any
}
func main() {
var users = []User{
{"John", 23},
{"Jane", 24},
{"Doe", 25},
}
var dict = Dict{
Meta: DictMeta{3, "List of users"},
Data: users,
}
fmt.Printf("dict: %v\n", dict)
marshaledDict, err := json.Marshal(dict)
var unmarshaledDict Dict
if err = json.Unmarshal(marshaledDict, &unmarshaledDict); err != nil {
panic(err)
}
// unmarshal されたデータを元の型に戻したい。
parsedDict, err := parseUnmarshaledDict(unmarshaledDict)
if err != nil {
panic(err)
}
fmt.Printf("parsedDict: %v\n", parsedDict)
}
func parseUnmarshaledDict(d Dict) (Dict, error) {
retDict := Dict{}
retDict.Meta = d.Meta
// parse Data
// d.Data を元の型に戻したい
//
// parsedDictData, ok := unmarshaledDict.Data.([]User) // <- これはできないため、失敗する
// if !ok {
// fmt.Printf("failed to parse unmarshaledDict.Data\n")
// panic(err)
// }
// fmt.Printf("parsedDictData: %v\n", parsedDictData)
// []any 型なので []any として型アサーションをし、更にそれぞれの要素を map[string]any として型アサーションをする
// 更にその上で、それぞれの要素(Name, Age)で型アサーションをして元の User 型に入れ直し、[]User となるように append を行う。
users := []User{}
anySlice, ok := d.Data.([]any)
if !ok {
return retDict, errors.New("failed to parse data")
}
for _, v := range anySlice {
userMap, ok := v.(map[string]any)
if !ok {
return retDict, errors.New("failed to parse user")
}
name, ok := userMap["Name"].(string)
if !ok {
return retDict, errors.New("failed to parse name")
}
age, ok := userMap["Age"].(float64)
if !ok {
return retDict, errors.New("failed to parse age")
}
user := User{Name: name, Age: int(age)}
users = append(users, user)
}
retDict.Data = users
return retDict, nil
}
上記の main.go を go run ./main.go などとして実行すると出力は下記のとおりです。
$ go run ./main.go
dict: {{3 List of users} [{John 23} {Jane 24} {Doe 25}]}
parsedDict: {{3 List of users} [{John 23} {Jane 24} {Doe 25}]}
まとめ
本記事では、Go言語における any
型データの元の型への復元方法を、「ユーザーリストのパース」を例に解説しました。このような手法を使うことで、JSON 形式でデータをシリアライズ・デシリアライズする際の柔軟性を保ちながら、型安全な操作が可能になります。また、any
型と型アサーションを組み合わせて柔軟かつ安全なデータ処理が実現できることがわかります。
Discussion