Добавление атрибута к вложенной карте [string] interface {} в Golang - PullRequest
0 голосов
/ 20 октября 2019

Я имею дело с данными типа map[string]interface{}. он может иметь неограниченное количество вложенных объектов внутри (map [string] interface {}) типов.

РЕДАКТИРОВАТЬ: Эти данные получены от mongodb. Я действительно не могу применить структуру Голанга здесь, потому что атрибуты варьируются от документа к документу. Все, что я хочу сделать, это получить наиболее глубоко вложенный объект, добавить в него новый атрибут и убедиться, что весь объект данных обновляется после.

data["person"] = map[string]interface{}{
    "peter": map[string]interface{}{
        "scores": map[string]interface{}{
            "calculus": 88,
            "algebra":  99,
            "golang":   89,
        },
    },
}

Эти данные поступают из удаленного APIи я понятия не имею о свойствах внутри. Все, что я хочу добавить, это добавить новый атрибут внутри последнего объекта (в данном случае «баллы»), и, скажем, с этим новым атрибутом («физика») данные будут выглядеть так:

data["person"] = map[string]interface{}{
    "peter": map[string]interface{}{
        "scores": map[string]interface{}{
            "calculus": 88,
            "algebra":  99,
            "golang":   89,
            "physics":  95,
        },
    },
}

IЯ не уверен, как я мог добавить этот атрибут к самому последнему объекту.

Я сделал рекурсивную проверку типов и смог получить каждое поле и вывести его значение. Но поскольку карты не являются ссылочными, я не могу добавить значение к исходной карте, когда достигну карты со значениями, которые не являются сложными типами.

package main

import "fmt"

func main() {

    data := make(map[string]interface{})
    data["person"] = map[string]interface{}{
        "peter": map[string]interface{}{
            "scores": map[string]interface{}{
                "calculus": 88,
                "algebra":  99,
                "golang":   89,
            },
        },
    }

    parseMap(data)
}


func parseMap(aMap map[string]interface{}) interface{} {
    var retVal interface{}

    for _, val := range aMap {
        switch val.(type) {
        case map[string]interface{}:
            retVal = parseMap(val.(map[string]interface{}))
        //case []interface{}:
        //  retVal = parseArray(val.([]interface{}))
        default:
            //here i would have done aMap["physics"] = 95 if I could access the original map by reference, but that is not possible

            retVal = aMap

        }
    }

    return retVal
}

Ответы [ 2 ]

1 голос
/ 20 октября 2019

Согласно комментариям к вопросу, цель состоит в том, чтобы установить значение в самой глубоко вложенной карте.

Используйте следующую функцию, чтобы найти карту с наибольшим уровнем вложенности. Если на самом большом уровне вложенности имеется более одной карты, эта функция возвращает произвольную одну из этих карт.

func findDeepest(m map[string]interface{}) (int, map[string]interface{}) {
    depth := 0
    candidate := m
    for _, v := range m {
        if v, ok := v.(map[string]interface{}); ok {
            d, c := findDeepest(v)
            if d+1 > depth {
                depth = d + 1
                candidate = c
            }
        }
    }
    return depth, candidate
}

Используйте его для установки значения на глубоко вложенной карте:

_, m := findDeepest(data)
m["physics"] = 95

Запустите его на детской площадке .

1 голос
/ 20 октября 2019

Старайтесь как можно больше избегать работы с необработанным типом map[string]interface{}. Файл Go encoding/json может отлично работать с картами со строковыми ключами, и, надеюсь, удаленный API имеет некоторую спецификацию для того, с чем вы имеете дело. (Вы знаете, что вы ожидаете person ключ верхнего уровня и scores в определенной точке в иерархии, например.)

Я предполагаю, что удаленный API-интерфейс является JSON-over-over-HTTP. Вы можете смоделировать ее структуру как

type Input struct {
    Person map[string]Person `json:"person"`
}

type Person struct {
    Scores map[string]int `json:"scores"`
}

После того, как вы json.Unmarshal() отредактировали данные в этой структуре, вы можете напрямую установить

data.Person["peter"].Scores["physics"] = 95

и затем json.Marshal() результат снова. https://play.golang.org/p/qoAVFodSvK2 имеет полный пример.

Если вы действительно хотите напрямую манипулировать структурой map[string]interface{}, я бы предложил разделить каждый "уровень" на отдельный вызов функции

func ParseTopLevel(data map[string]interface{}) {
    switch peter := data["peter"].(type) {
    case map[string]interface{}:
        ParsePeter(peter)
    }
}

map типы передаются по ссылке , поэтому, когда вы достигнете дна стека, вы можете напрямую установить scores["physics"] = 95. (В вашем исходном коде я был бы удивлен, если вы не можете напрямую установить aMap["physics"], как вы предлагаете, хотя это довольно неточно в отношении того, что устанавливается; сравните https://play.golang.org/p/VuTjcjezwwU.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...