简体   繁体   中英

How to type assert a dynamically reflection-generated struct interface

I'm new to Go so please bear with me if this is a trivial problem. I am using a home grown "type registry" to map type names to their type, so as to generate them dynamically based on use cases that point to the various type names (I'm basically trying for a simple solution to polymorphic Aggregation JSON response structures in Elasticsearch, but of course this could apply to many other dynamic/polymorphic situations). I'm using the solution provided by dolmen in this question: is there a way to create an instance of a struct from a string? :

var typeRegistry = make(map[string]reflect.Type)

func registerType(typedNil interface{}) {
    t := reflect.TypeOf(typedNil).Elem()
    typeRegistry[t.Name()] = t
}

func init() {
    registerType((*playlistIDAggregation)(nil))
    registerType((*srcIDAggregation)(nil))
    registerType((*assetIDAggregation)(nil))
}

func makeInstance(name string) interface{} {
    return reflect.New(typeRegistry[name]).Elem().Interface()
}

I then want to use my dynamically generated struct as the target for the JSON unmarshalling of the Aggregations node in my ES response:

playlistIDAgg := makeInstance("playlistIDAggregation")
err = json.Unmarshal(esResponse.Aggregations, &playlistIDAgg)

This isn't working like I want it to, as the Unmarshal is trying to unmarshall into an empty interface instead of the underlying struct type. it's putting the data under "data" nodes in the playlistIDAgg variable, and those data fields are of course map[string]interface{} . Am I just missing the way to type assert my playlistIDAgg interface or is there a better way to do this?

EDIT--- The questions in the comments made me realize an edit to this question was long overdue. In my particular case, the structs I defined, to bind to my Bucket aggregations returned by Elasticsearch, have a similar structure and vary only by their root JSON tag, which ES uses to name the aggregation and strongly type it. Eg

type <name>Aggregation struct {
    Agg BucketAggregationWithCamIDCardinality `json:"<name>"`
}

So, rather than a type registry, my particular problem could be solved by dynamically setting the JSON tag on the struct based on the particular use case.

In addition, a heavier but more robust option would be to leverage Oliver Eilhard's Elasticsearch Go client lib, called Elastic, which has built-in support for all the ES aggregation response structures: https://github.com/olivere/elastic/

I changed makeInstance function by getting address of element and add target structs with exposed fields.

func makeInstance(name string) interface{} {
    return reflect.New(typeRegistry[name]).Elem().Addr().Interface()
}

Here is the working code

package main

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

type playlistIDAggregation struct {
    PlaylistID string
}

type srcIDAggregation struct {
    SrcID string
}

type assetIDAggregation struct {
    AssetID string
}

var typeRegistry = make(map[string]reflect.Type)

func registerType(typedNil interface{}) {
    t := reflect.TypeOf(typedNil).Elem()
    typeRegistry[t.Name()] = t
}

func init() {
    registerType((*playlistIDAggregation)(nil))
    registerType((*srcIDAggregation)(nil))
    registerType((*assetIDAggregation)(nil))
}

func makeInstance(name string) interface{} {
    return reflect.New(typeRegistry[name]).Elem().Addr().Interface()
}

func main() {
    playlistIDAgg := makeInstance("playlistIDAggregation")
    fmt.Printf("Type = %[1]T => %#[1]v\n", playlistIDAgg)
    err := json.Unmarshal([]byte(`{"PlayListID": "dummy-id"}`), &playlistIDAgg)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Type = %[1]T => %#[1]v\n", playlistIDAgg)
}

https://play.golang.org/p/dn19_iG5Xjz

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM