Search code examples
jsongoiteration

How do I do a nested iteration


I'm trying to work on an extention for another software that sends a request to an application made in Go. In the Go program (which I'll now refer to as "the program"), one purpose is to convert a JSON file into a format that can be iterated through. Here is an example JSON format I am working with:

{
  "name": "Game-Name",
  "tree": {
    "$className": "DataModel",

    "ReplicatedStorage": {
      "$path": "src/ReplicatedStorage"
    },

    "ServerScriptService": {
      "$path": "src/ServerScriptService"
    },

    "ReplicatedFirst": {
      "$path": "src/ReplicatedFirst"
    },

    "ServerStorage": {
      "$path": "src/ServerStorage"
    }
  }
}

The idea is that:

  • The iteration can pick up the "name"
  • The iteration can pick up the "$className"
  • For all instances of "$path" as an index, a folder is created under a parent src folder with the index of the parent map. For example, ReplicatedStorage is the name of a folder that has the path src/ReplicatedStorage

Below is a process function intended to do this:

func process(in interface{}) {
v := reflect.ValueOf(in)

    if v.Kind() == reflect.Map {
        for _, key := range v.MapKeys() {
            strct := v.MapIndex(key)
    
            index := key.Interface()
            value := reflect.ValueOf(strct.Interface())
    
            if index == "tree" {
                for _, treeKey := range value.MapKeys() {
                    treeIndex := treeKey.Interface()
    
                    fmt.Println("KEY")
                    fmt.Println(treeIndex)
    
                    if treeIndex != "$className" {
                        fmt.Println("bug")
                        fmt.Println(treeKey)
    
                        a := key.MapIndex(value) // panic serving ...: reflect: call of reflect.Value.MapIndex on string Value
                        b := reflect.ValueOf(a.Interface())
    
                        for _, key2 := range b.MapKeys() {
                            index2 := key2.Interface()
                            value2 := reflect.ValueOf(key2.Interface())
    
                            fmt.Println(index2)
                            fmt.Println(value2)
                        }
                    }
                }
            }
        }
    }

}

The comment is where and what the error is. One thing I would also want to ideally do is to not have to stack for-loops, since that's pretty stinky code.


Solution

  • The usual approach is to unmarshal to Go types that match the structure of the data. The problem here is that the tree cannot easily be represented as a Go type (it has field $classname with string type, but is otherwise like a map with object values containing $path field).

    Let's proceed with unmarshaling to interface{} as you have already done.

    Use type assertions instead of the reflect package. Use map indexing to find values instead of looping through keys and looking for a match.

    func process(in interface{}) error {
        top, ok := in.(map[string]interface{})
        if !ok {
            return errors.New("expected object at top level")
        }
        tree, ok := top["tree"].(map[string]interface{})
        if !ok {
            return errors.New(".tree not found")
        }
        name, ok := top["name"]
        if !ok {
            return errors.New(".name not found")
        }
        className, ok := tree["$className"].(string)
        if !ok {
            return errors.New(".tree.$className not found")
        }
        for k, v := range tree {
            thing, ok := v.(map[string]interface{})
            if !ok {
                continue
            }
            path, ok := thing["$path"].(string)
            if !ok {
                continue
            }
            fmt.Println(name, className, k, path)
        }
        return nil
    }
    

    https://go.dev/play/p/9GFpccjNQZY