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())
2
  • Uploaded pure code. Commented Feb 23, 2018 at 14:17
  • Thx. all. I found the answer. should be CheckNestedStruct(field.Addr().Interface()) Commented Feb 23, 2018 at 14:31

3 Answers 3

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))
Sign up to request clarification or add additional context in comments.

2 Comments

Hi, thx for the answer. I found the answer. Your answer is close, but, should be field.Addr().Interface()
@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.
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

Comments

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
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.