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