在go结构中初始化深层地图嵌套

To initialize a map in a struct one should do the following:

someStruct.nestedMap = make(map[int8]int8)

But what should you do if you have a code structure like this:

type Base struct {
    base map[int8]uint64
}

type Middle struct {
    baseObjects map[int8]Base
}

type Top struct {
    middleObjects map[int8]Middle
}

There we have a total of 3 structs where each has a struct as a key. How do you initialize this, and make it ready to go?

What you want is auctually a map with default value that is not zero value, but a value ready to use, in this case, a made map.

Before going to the solution, there auctually is an issue about go here: https://github.com/golang/go/issues/3117

For reasons, Go now does not support to assign to a field of a struct if the struct is stored in a map.

To solve the issue, you need to use a pointer to that struct, and store the pointer in the map instead. So your data structure needs to be changed like:

type Base struct {
    base map[int8]uint64
}

type Middle struct {
    baseObjects map[int8]*Base
}

type Top struct {
    middleObjects map[int8]*Middle
}

And to the core logic: a defualt value map. Go does not support that with plain maps, but we can extend it, using methods to wrap it around.

func (t Top) Get(i int8) *Middle {
    x, ok := t.middleObjects[i]
    if !ok {
        v := NewMiddle()
        t.middleObjects[i] = v
        return v
    }

    return x
}

func (m Middle) Get(i int8) *Base {
    x, ok := m.baseObjects[i]
    if !ok {
        v := NewBase()
        m.baseObjects[i] = v
        return v
    }

    return x
}

When we are trying to get a value, we first check if it exists. If not, we return a new constructed one, and if it did exist, return the value.

Usage:

t.Get(8).Get(9).base[10] = 14

Playground example: https://play.golang.org/p/0JSN0yjRPif

Simply initialize the topmost struct Top with an empty map as value for its middleObjects property, calling any index on an empty map returns a zero value for the type the map holds.

package main

import (
    "fmt"
)

type Base struct {
    base map[int8]uint64
}

type Middle struct {
    baseObjects map[int8]Base
}

type Top struct {
    middleObjects map[int8]Middle
}

func main() {
    top := Top{
        middleObjects: make(map[int8]Middle),
    }

    // Outputs: "Top: {map[]}"
    fmt.Printf("Top: %v
", top)

    // Outputs: "Base element: 0"
    fmt.Printf("Base element: %v
", top.middleObjects[5].baseObjects[3].base[0])
}

Run the example here


Edit:

You could also initialize the Top struct with a couple of elements, in any case it depends on how many elements you require each map to have at initialization:

func main() {
    top := Top{
        middleObjects: map[int8]Middle{
            0: Middle{
                baseObjects: map[int8]Base{
                    0: Base{
                        base: map[int8]uint64{
                            0: 1234,
                        },
                    },
                },
            },
        },
    }

    // After that you have to add one by one
    top.middleObjects[5] = Middle{
        baseObjects: map[int8]Base{
            0: Base{
                base: map[int8]uint64{
                    0: 123456,
                },
            },
        },
    }
    // If there's a middleObject with that index
    top.middleObjects[0].baseObjects[1] = Base{
        base: map[int8]uint64{
            0: 1111,
        },
    }

    // Outputs: "Base element: 1234"
    fmt.Printf("Base element: %v
", top.middleObjects[0].baseObjects[0].base[0])
    // Outputs: "Base element: 123456"
    fmt.Printf("Base element: %v
", top.middleObjects[5].baseObjects[0].base[0])
    // Outputs: "Base element: 1111"
    fmt.Printf("Base element: %v
", top.middleObjects[0].baseObjects[1].base[0])

    top.middleObjects[0].baseObjects[0].base[0] = 2222
    // Outputs: "Base element: 2222"
    fmt.Printf("Base element: %v
", top.middleObjects[0].baseObjects[0].base[0])
}

Run example here