Как проверить, что переменная, объявленная как map [string] interface {}, на самом деле является строкой map [string]? - PullRequest
1 голос
/ 02 ноября 2019

У меня есть переменная, которая должна быть либо string, либо map[string]string (будет десериализована из JSON). Поэтому я объявляю это как interface{}. Как я могу проверить, что значение равно map[string]string?

Этот вопрос Как проверить, что интерфейс представляет собой строку карты [строки] в golang почти отвечает на мой вопрос. Но принятый ответ работает, только если переменная объявлена ​​как map[string]string, а не, если переменная interface{}.

package main

import (
    "fmt"
)

func main() {

    var myMap interface{}
    myMap = map[string]interface{}{
        "foo": "bar",
    }
    _, ok := myMap.(map[string]string)
    if !ok {
        fmt.Println("This will be printed")
    }
}

См. https://play.golang.org/p/mA-CVk7bdb9

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

package main

import (
    "fmt"
)

func main() {
    var myMap interface{}
    myMap = map[string]interface{}{
        "foo": "bar",
    }

    valueMap, ok := myMap.(map[string]interface{})
    if !ok {
        fmt.Println("will not be printed")
    }
    for _, v := range valueMap {
        if _, ok := v.(string); !ok {
            fmt.Println("will not be printed")
        }
    }
}

См. https://play.golang.org/p/hCl8eBcKSqE

Вопрос: есть ли лучший способ?

1 Ответ

2 голосов
/ 02 ноября 2019

Если вы объявляете переменную как тип interface{}, она равна типу interface{}. Это не является , когда-либо, некоторым значением map[keytype]valuetype. Но переменная типа interface{} может содержать значение, которое имеет какой-то другой конкретный тип. Когда он это делает, он делает это - вот и все, что нужно сделать. Он по-прежнему является типом interface{}, но содержит значение некоторого другого типа.

Значение интерфейса состоит из двух частей

КлючРазличие здесь заключается в том, что interface{} переменная является , и что она содержит . Любая переменная интерфейса на самом деле имеет два слота внутри: один для хранения того, что тип хранится в нем, и один для хранения того, что значение хранится в нем. Каждый раз, когда вы или кто-либо другой присваиваете значение переменной , компилятор заполняет оба слота: тип, из типа используемого вами значения, и значение из используемого вами значения. 1 Переменная интерфейса сравнивается равной нулю, если она имеет nil в обоих слотах;и это также нулевое значение по умолчанию.

Следовательно, ваш тест во время выполнения:

valueMap, ok := myMap.(map[string]interface{})

- разумная вещь: если myMap содержит значение типа map[string]interface,ok устанавливается в true, а valueMap содержит значение (которое имеет этот тип). Если myMap содержит значение с другим типом, ok устанавливается в false, а valueMap устанавливается в нулевое значение типа map[string]interface{}. Другими словами, во время выполнения код сначала проверяет слот типа, затем либо копирует слот значения в valueMap и устанавливает ok в true, либо устанавливает valueMap в nil и устанавливает okв false.

Если и когда ok было установлено на true, каждое valueMap[k] значение равно типу interface{}. Как и прежде, для самого myMap каждая из этих interface{} переменных может - но не обязана - содержать значение типа string, и вы должны использовать какое-то "что такоефактический тип-значение "во время выполнения теста, чтобы разделить их на части.

Когда вы используете json.Unmarshal для помещения декодированного JSON в переменную типа interface{}, он способендесериализации любого из этих документированных типов JSON. Затем список сообщает, что type вставляется в переменную интерфейса:

bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null

Так что после выполнения json.Unmarshal в переменную типа interface{},Вы должны проверить, какой тип был помещен в слот типа переменной. Вы можете сделать это с помощью утверждения и логического значения ok, или, если хотите, можете использовать переключатель типа для его декодирования:

var i interface
if err := json.Unmarshal(data, &i); err != nil {
    panic(err)
}
switch v := i.(type) {
case string:
    ... code ...
case map[string]interface{}:
    ... code ...
... add some or all of the types listed ...
}

Дело в том, что независимо от того, что вы делаете в коде, вы сделали json.Unmarshal положили что-то в interface{}, а interface{} - это типа i. Вы должны проверить во время выполнения, какую пару типов и значений поддерживает интерфейс.

Другой вариант - вручную проверить строки JSON и решить, какой тип переменной предоставить json.Unmarshal. Это дает вам меньше кода для записи после Unmarshal, но больше кода для записи до it.

Здесь есть более полный пример здесь, наигровая площадка Go , в которой используются переключатели типа для проверки результата с json.UnmarshalОн намеренно неполон, но, думаю, у него достаточно вариантов ввода и вывода, чтобы вы могли понять, как обрабатывать все, учитывая приведенную выше цитату о том, что json.Unmarshal записывает в переменную типа interface{}.


1 Конечно, если вы присваиваете один interface{} из другого interface{}:

var i1, i2 interface{}
... set i1 from some actual value ...
// more code, then:
i2 = i1

, компилятор просто копирует оба слота из i1 в i2. Когда вы выполняете: два отдельных слота становятся понятнее, например:

var f float64
... code that sets f to, say, 1.5 ...
i2 = f

, так как это записывает float64 в слот типа, а значение 1.5 в слот значения. Компилятор знает , что f равен float64, поэтому настройка типа просто означает "вставить в нее константу". Компилятор не обязательно знает значение из f, поэтому установка значения является копией любого фактического значения.

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