-1

e.g. Assuming the interface{} object is a struct {"a":1, "b": "test", c: &AnotherStruct{}}, and we need to iterate the object to get value of each field "a", "b", "c".

I can think of two ways:

  1. use Go reflection directly.
  2. use json.Marshal()/json.Unmarshal() to convert the object to map[string]interface{}, and then iterate over the map to do type assertions, this also calls reflection, however there might be some json library having optimizations inside which might gain better performance, e.g. https://github.com/bytedance/sonic.

I was wondering which one is more efficient and is there any other way to do it?

Tate
  • 53
  • 9

1 Answers1

-2

interface{} is (legacy) go short-hand for "this could be anything". It does not represent an "object" (though it could). From go 1.18 onward the keyword any was introduced as a direct replacement for interface{} (though the latter may continue to be used if you need compatibility with older golang versions).

Here-on I shall use any for brevity.

I'd suggest ignoring efficiency unless/until it becomes a problem you need to solve and instead focus on the clearest and simplest way to achieve what you need, functionally.

It is difficult to be clear about what exactly you are faced with and trying to achieve; your "example" object contains both quoted and unquoted field members so I see four possible scenarios that you may be dealing with:

  1. an any variable holding a value that is of a known formal struct type
  2. an any variable holding a value that is of an anonymous formal struct type
  3. an any variable holding a JSON string
  4. an any variable holding a map[string]any

For scenario's 1 and 2 that would be to marshal to JSON then unmarshal to map[string]any.

For scenario 3 you would cast to string then unmarshal to map[string]any:

For scenario 4 you would directly cast to map[string]any.

I have worked up all of these in a playground for you here: https://go.dev/play/p/cSdUmynTFRp

package main

import (
    "encoding/json"
    "fmt"
)

type AnotherStruct struct {
    X int `json:"x"`
}

type Foo struct {
    A int           `json:"a"`
    B string        `json:"b"`
    C AnotherStruct `json:"c"`
}

func emit(m map[string]any) {
    for k, v := range m {
        fmt.Printf("  %s: %s\n", k, v)
    }
    fmt.Println()
}

func scenario1or2(n int, foo any) map[string]any {
    fmt.Printf("scenario %d:\n", n)

    j, _ := json.Marshal(foo)

    m := map[string]any{}
    json.Unmarshal(j, &m)

    return m
}

func scenario3(foo any) map[string]any {
    fmt.Println("scenario 3")

    m := map[string]any{}
    json.Unmarshal([]byte(foo.(string)), &m)

    return m
}

func scenario4(foo any) map[string]any {
    fmt.Println("scenario 4")

    return foo.(map[string]any)
}

func main() {
    emit(scenario1or2(1, Foo{
        A: 1,
        B: "test",
        C: AnotherStruct{X: 42},
    }))

    emit(scenario1or2(2, struct {
        A int
        B string
        C AnotherStruct
    }{
        A: 1,
        B: "test",
        C: AnotherStruct{
            X: 42,
        },
    }))

    emit(scenario3(`{"a":1,"b":"test","c":{"x":42}}`))

    emit(scenario4(map[string]any{
        "a": 1,
        "b": "test",
        "c": AnotherStruct{
            X: 42,
        },
    }))
}

If you have scenario 1 and simply want efficient access to the fields (i.e. iterating over potentially unknown fields is not actually required) then you can type-cast directly to the known formal struct type:

   // assuming...
   type Foo struct {
     a int
     b string
     c *AnotherStruct {
     }
   }

   // and where 'anyfoo' is an `any` holding a Foo
   foo := anyfoo.(Foo)

   a := foo.a
   b := foo.b
   c := foo.c
Deltics
  • 22,162
  • 2
  • 42
  • 70
  • There's really no reason to involve JSON here. It seems to just be a red herring from OP guessing at what might work. – Hymns For Disco Jan 11 '23 at 05:10
  • Except that the example "object" that the OP provided involves fields with quoted names, suggesting that JSON is *already* involved, just not made clear in the question. (although admittedly one of the fields was *not* quoted and referenced a struct type, somewhat confusing things). Plus, leveraging the JSON marshalling to do all the reflecting work simplifies the task. I know the OP asked for the "most efficient" mechanism, but this smells of premature optimisation (as referenced in my answer). – Deltics Jan 11 '23 at 07:56
  • It seems they just made a mistake and added some quote marks when typing the Go syntax. For example, they say that it's a struct, and `&AnotherStruct{}` is clearly a Go struct pointer literal. – Hymns For Disco Jan 11 '23 at 08:01
  • They also seemed to conflate "interface{}" with "object", suggesting they were pretty new to Go in general. I was just trying to be helpful. But I see this Q has been duped now, so nothing to see here. :) – Deltics Jan 11 '23 at 08:03