How to use reflect to recursively parse nested struct in Go?
Asked Answered
A

3

7

I have a nested three layer struct. I would like to use reflect in Go to parse it (use recursive function). The reasons to use reflect and the recursive function are

  • can have various number of fields (but the first two fields are fixed)
  • the field types are not fixed.
  • The number of nested layers can be different (in this example only three layers. It can be many more)

Here are some codes.

type Edge struct{
    Uid string `json:"uid,omitempty"`
    Name string `json:"name,omitempty"` 
    Read Article `json:"visited,omitempty"` 
} 
type Article struct {
    Uid string`json:"uid,omitempty"` 
    Namestring`json:"name,omitempty"`
    From Site `json:"from,omitempty"`
}
type Site struct{
    Uid string `json:"uid,omitempty"`
    Name string `json:"name,omitempty"` 
}
func CheckNestedStruct(edges interface{}){ 
    rv := reflect.ValueOf(edges).Elem() 
    uidField := rv.FieldByName("Uid")
    uid := getStructField(edges, "Name") // get value of Name from database 
    if (uid != ""){
        uidField.SetString(uid)
    }
    for i := 0 ; i < rv.NumField() ; i++ {
        field := rv.Field(i)
        fieldType := field.Kind()
        if (fieldType == reflect.Struct){
            CheckNestedStruct(field)
        }
    }
}
func main(){
    ....
    var edges Edges{
    ...
    ...
    }
    CheckNestedStruct(&edges)
}

When I ran this, in the first layer I got "type: *entity.SacWebIS". However, in the second iteration/recursion, I got "type: *reflect.rtype" . I also tried field.Interface(). How to modify this code? Thanks.

UPDATE

The solution is

CheckNestedStruct(dg, field.Addr().Interface())
Azrael answered 23/2, 2018 at 14:10 Comment(2)
Uploaded pure code.Azrael
Thx. all. I found the answer. should be CheckNestedStruct(field.Addr().Interface())Azrael
P
8

You are calling reflect.ValueOf on a reflect.Value, which is what gives you the type *reflect.rtype. If you want to pass the reflect.Value back to the same function, you need to first call Interface().

CheckNestedStruct(field.Interface())

You then are calling Elem regardless of whether you're operating on a pointer or a value. If you want to conditionally indirect a value, use reflect.Indirect

rv := reflect.Indirect(reflect.ValueOf(edges))
Promising answered 23/2, 2018 at 14:25 Comment(2)
Hi, thx for the answer. I found the answer. Your answer is close, but, should be field.Addr().Interface()Azrael
@LuffyCyliu: it depends on what you want to accomplish. The Addr is not required if you use Indirect, and it will panic if you encounter a field that not addressable.Promising
D
2

For parsing unknown json without knowing the values and types of the field you needs to create a recursive function which will parse through deeply nested underlying value. You can get final value using type assertion .

func main() {
    m, ok := myJson.(map[string]interface{})
    newM := iterate(m)
    jsonBytes, err := json.Marshal(newM)
    if err != nil {
            fmt.Println(err)
    }
    fmt.Println(string(jsonBytes))
}

For the record on unmarshaling a json to an interface It converts mainly in two types for nested structure either slice of interface []interface{} or map of interface map[string]interface{} until we get final value of nested structure which we can get using Interface().Hence we can create a recursive for deep nested structures.

func iterate(data interface{}) interface{} {

    if reflect.ValueOf(data).Kind() == reflect.Slice {
            d := reflect.ValueOf(data)
            tmpData := make([]interface{}, d.Len())
            returnSlice := make([]interface{}, d.Len())
            for i := 0; i < d.Len(); i++ {
                    tmpData[i] = d.Index(i).Interface()
            }
            for i, v := range tmpData {
                    returnSlice[i] = iterate(v)
            }
            return returnSlice
    } else if reflect.ValueOf(data).Kind() == reflect.Map {
            d := reflect.ValueOf(data)
            tmpData := make(map[string]interface{})
            for _, k := range d.MapKeys() {
                    typeOfValue := reflect.TypeOf(d.MapIndex(k).Interface()).Kind()
                    if typeOfValue == reflect.Map || typeOfValue == reflect.Slice {
                            tmpData[k.String()] = iterate(d.MapIndex(k).Interface())
                    } else {
                            tmpData[k.String()] = d.MapIndex(k).Interface()
                    }
            }
            return tmpData
    }
    return data
}

At last for underlying value of an interface{} which will be of primitive type string, float64, bool.

func identify(output map[string]interface{}) {
    fmt.Printf("%T", output)
    for a, b := range output {
        switch bb := b.(type) {
        case string:
            fmt.Println("This is a string")
        case float64:
            fmt.Println("this is a float")
        case bool:
            fmt.Println("this is a boolean")
        case []interface{}:
        // Access the values in the JSON object and place them in an Item
        for _, itemValue := range jsonObj {
            fmt.Printf("%v is an interface\n", itemValue)
            identify(itemValue.(map[string]interface{}))
        }
        default:
            return
        }
    }
}

Check it on Go Playground

Disregard answered 23/2, 2018 at 14:23 Comment(0)
S
0

A simpler version of the above code (iterate function):


func iterate(data interface{}) interface{} {
    d := reflect.ValueOf(data)
    if reflect.ValueOf(data).Kind() == reflect.Slice {
        returnSlice := make([]interface{}, d.Len())
        for i := 0; i < d.Len(); i++ {
            returnSlice[i] = iterate(d.Index(i).Interface())
        }
        return returnSlice
    } else if reflect.ValueOf(data).Kind() == reflect.Map {
        tmpData := make(map[string]interface{})
        for _, k := range d.MapKeys() {
            tmpData[k.String()] = iterate(d.MapIndex(k).Interface())
        }
        return tmpData
    } else {
        return data
    }
}
Sazerac answered 5/7, 2019 at 9:13 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.