🧩

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 関数を実装します。この関数では、以下の手順でデータを復元します。

  1. any 型として格納されているデータをスライス型 []any にアサーションします。
  2. 各要素を map[string]any 型として取り出し、User 構造体のフィールドに対応する値にアサーションします。
  3. それぞれの要素を 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