8 ms·
Map[string]interface{} in Go: a user's manual
- nixpulvis 5y ago> What's the point of interface{}, then, if it doesn't tell us anything about the value? Well, that's precisely why it's useful: it can refer to anything! An empty interface should be effectively useless. What could it possibly do!? This pattern is much more like a dynamic container with a type tag.
- tptacek 5y agoDoing nothing is the point; it serves the same purpose as a `void*` in C, except that there are (runtime-) safe ways to take a value out of `interface{}` and back to its concrete type in Go. The most obvious use for it is heterogenous containers.
- nixpulvis 5y agoRuntime safe and statically type checked are two fundamentally different things is my point. Interfaces in most typed languages are static concepts, though there are always exceptions.
- strbean 5y agoYou can think of interface{} like Object in Java. Interfaces in go are simply saying "this thing satifies these properties". interface{} is an interface that anything can satisfy.
- nixpulvis 5y agoI will continue to think of interface{} as Any in Rust.
- ibraheemdev 5y ago`interface{}` used as a type is almost exactly `Box<dyn Any>`.
- throw_m239339 5y ago> The most obvious use for it is heterogenous containers. You mean runtime generic container without the benefits of compile-time type checking, opening the developer to runtime type errors purely for ideological reasons...
- a1369209993 5y ago> opening the developer to runtime type errors purely for ideological reasons... To be fair, there are also language-implementor-laziness reasons.
- earthboundkid 5y agoThat Go would benefit from generics doesn't change the fact that there are also lots of use for heterogenous containers. As it is, Go already has "slices" for type safe containers. The fair complaint about Go is it's hard to do other type safe container than the built in "slice" and "map" containers. But the problem is on track to be fixed next year, so it's silly to complain about it now.
- earthboundkid 5y agoThat Go would benefit from generics doesn't change the fact that there are also lots of use for heterogenous containers. As it is, Go already has "slices" for type safe containers. The fair complaint about Go is it's hard to do other type safe container than the built in "slice" and "map" containers. But the problem is on track to be fixed next year, so it's silly to complain about it now. It's really hard to take people who complain about Go seriously when they're pretty obviously just zealots who don't know much about the language besides "types good" and "Go bad". My stance: types good in moderation; Go a useful tradeoff of engineering attributes.
- throw_m239339 5y ago> so it's silly to complain about it now. No it isn't, Go has other flaws stemming from the exact same root cause and it has nothing to do with programming...
- tptacek 5y ago
- jrockway 5y agoI don't think "empty" is quite the right mental model. It's an object that has at least 0 methods, which is a characteristic of every data type. An actually empty type in Go is struct{} (good for map values when the map represents a set).
- LambdaComplex 5y agoTo be clear, "empty interface" is the official term: ctrl+f "empty interface" on https://golang.org/ref/spec https://golang.org/ref/spec
- ibraheemdev 5y agoWell the interface's method set is empty, not the underlying type.
- jhoechtl 5y agoThis article would be great to be revised once parametric types (AKA "generics") become an official part of the language. https://go.googlesource.com/proposal/+/master/design/15292/2013-12-type-params.md https://go.googlesource.com/proposal/+/master/design/15292/2... https://go2goplay.golang.org/ https://go2goplay.golang.org/
- kjksf 5y agomap in Go is already generic. If you need map[string]int or map[int]float, you can already do it. Generics do not obviate the need for interface{} or the described use case of map[string]interface{} interface{} is like Java or C# object. It packages a type and a value in a single value that can be queried at runtime to extract the underlying value in a type safe matter. When people use map[string]interface{] it's because they don't know the type of value at compile time.
- cle 5y agoI'm not sure why you're being downvoted, the only alternatives I've seen are to use methods or enums, which constrain the types to those supported by JSON, but ultimately you still have to switch on the type at runtime, the tradeoff being between closed enums vs. open interfaces, or the runtime tradeoffs of methods. Rust's serde_json uses enums for this, but the maps are generic in the same way that they already are in Go.
- ibraheemdev 5y ago> the maps are generic in the same way that they already are in Go. Go maps are actually not generic in the same way they are in other languages, there is just a single map struct with type descriptors used to do some compiler magic: https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics https://dave.cheney.net/2018/05/29/how-the-go-runtime-implem...
- deleted 5y ago[deleted]
- Blikkentrekker 5y agoThis article almost seems like a falseflag, in how it cheerfully provides a benediction for an ugly hack to deal with controversial inadequacies of the Go type system.
- kevincox 5y agoI don't see interface{} as a deficiency in the type system. Just about every type system has a method for storing "any" value such that it can then be upcast at runtime. The problem in Go isn't with interface{} but that the simple type system can't handle common use cases so you end up needing it use interface{} much more often that you would need to in other languages. Basically interface{} is useful when you truly need it. But it is often used to workaround deficiencies in the type system.
- hdhjebebeb 5y agoI'm excited for generics to land in Golang so we can have efficient set implementations instead of abusing maps.
- darkr 5y agoIn many languages (including Python & Scala), sets are not much more than syntactic sugar on a hashmap-like datatype.
- nynx 5y agoIn rust, hashsets are just hashmaps with a zero-sized type as the value, which results in all the extra getting optimized away.
- BenTheElder 5y agoA zero-sized type + hashmap for sets is also done in Go with a `struct{}`. [1][2] But without generics it must be implemented again for each entry type ... Kubernetes uses a code generator to automate this, but it's a bit kludge. [3] [1]: https://pkg.go.dev/k8s.io/apimachinery/pkg/util/sets#Empty https://pkg.go.dev/k8s.io/apimachinery/pkg/util/sets#Empty [2]: https://dave.cheney.net/2014/03/25/the-empty-struct https://dave.cheney.net/2014/03/25/the-empty-struct [3]: https://github.com/kubernetes/code-generator/blob/master/cmd/set-gen/main.go https://github.com/kubernetes/code-generator/blob/master/cmd...
- cmckn 5y agoThis is true for Java as well, a `HashSet` is just a `HashMap<T, Object>` where the value is: // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/HashSet.java#L97 https://github.com/openjdk/jdk/blob/master/src/java.base/sha... A thread-safe implementation is available with `ConcurrentHashMap.newKeySet()`, which uses a similar approach: public static <K> KeySetView<K,Boolean> newKeySet() { return new KeySetView<K,Boolean> (new ConcurrentHashMap<K,Boolean>(), Boolean.TRUE); } https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/HashSet.java#L97 https://github.com/openjdk/jdk/blob/master/src/java.base/sha...
- enricozb 5y ago`map[string]interface{}` is just a map with string keys and `any` as a value, and is commonly used as the type for a JSON object. Unless you have a completely specified type for your JSON object (at that point might as well use a `struct` with all of the fields defined), the best you _could_ do would be something like a recursive type `type JSON = bool | string | []JSON | map[string]JSON` in some hypothetical version of Golang with recursive types and unions. Also, if you're making a set, use `map[string]struct{}` instead of `map[string]interface{}` as `struct{}` takes 0 bytes whereas `interface{}` takes 8.
- TJSomething 5y agoYou could have a JSON value interface with methods like getType and getAsString, which would remove the overhead and type safety issues of reflection.
- andrewmcwatters 5y ago`map[string]interface{}` screams disconnected from reality when building web software. What an awful experience.
- deleted 5y ago[deleted]
- tptacek 5y agoI only rarely ever use `interface{}` anywhere in Go code. The idiomatic thing to do with JSON messages you're going to hold onto in Go web code is to type them, just as you would in Rust with serde.
- andrewmcwatters 5y agoYeah, nah. I’m not typing 5 levels of “structs” for one or two properties I want.
- nkozyra 5y agoYeah, the formality of embedded structs can lead to some very verbose bloat. And it obviously only works on very reliable payloads.
- tptacek 5y agoWhat's the solution for an unreliable payload? When things aren't where you expect them in a JSON blob with a Python Flask app, your app also just breaks on that input.
- tgsovlerkhgsel 5y agotry: val = input['foo']['bar'] except KeyError, TypeError: raise HttpErrors.BadRequest
- 5y ago
- sigmonsays 5y agodepending on what you're trying to do a map[string]interface{} is not really want you want. Most likely you want map[string]json.RawMessage if the key can indicate what the next step is in umarshalling. if the serialized structure doesn't indicate its "Type" somewhere/somehow then you would have to resort to map[string]interface{}
- stevebmark 5y agoWhy does the syntax specifically use `interface{}` instead of just `interface`? `{}` isn't use for any other types
- ibraheemdev 5y ago`interface` isn't a type, it's the keyword used to declare an interface: type Foo interface { Foo() int } `interface{}` is simply a shortform for an interface with no methods.
- akavel 5y agoThere's a nice trick you can use to make your map[string]interface{} code a bit easier to write and arguably also to read (less {}s), useful especially when you need e.g. to serialize some deeply nested JSON, for one-off request: func doSomeRequest(...) { type loose map[string]interface{} payload, err := json.Marshal(loose{ "fooBar": true, "nested": loose{ "subfield": bazArg, }, }) // ... } I should really probably write some blog listing various tricks like this I'm using, but I still can't make myself do it...
- scruple 5y agoThat is a good trick and I have a place where I can use this idea to make something more clear. You should write that blog post!