I'm a Javascript, Lua, Python, and C# guy and Dict is my whole world.
I'm a Javascript, Lua, Python, and C# guy and Dict is my whole world.
What the poster was alluding to is that you usually prefer to deseialize to a struct rather than a record/dict/map
No, Go doesn't have a type named Dict, or Hash (my Perl is leaking), or whatever.
It does have a map type[1], where you can define your keys as one type, and your values of another type, and that pretty closely approximates Dicts in other languages, I think.
> arbitrary JSON is the opposite of strongly typed
On the surface, I agree. In practice, many big enterprise systems use highly dynamic JSON payloads where new fields are added and changed all the time. var v map[string]any
json.Unmarshal(myjson, &v)
lst := v["list"].([]any)
firstItem := lst[0].(map[string]any)
field := firstItem["field"].(float64)
And this is without any error checking (this code will panic if myjson isn't a json byte array, if the keys and types don't match, or if the list is empty). If you want to add error checking to avoid panics, it gets much longer [0].Here is the equivalent Python with full error checking:
try :
v = json.loads(myjson)
field = v["list"][0]["list"]
except Exception as e:
print(f"Failed parsing json: {e}")
[0] https://go.dev/play/p/xkspENB80JZhttps://en.wikipedia.org/wiki/Hash_table
They're a very common and useful idea from Computer Science so you will find them in pretty much any modern language, there are a lot of variations on this idea, but the core idea recurs everywhere.
The basic operations of a hash table are adding a new item to the hash table, and checking if an item is present (potentially removing it as well). A hash table doesn't naturally have a `V get(key K)` function, it only naturally has a `bool isPresent(K item)` function.
This is all relevant because not all maps use hash tables (e.g. Java has TreeMap as well, which uses a red-black tree to store the keys). And there are uses of hash tables besides maps, such as a HashSet.
Edit: the term "table" in the name refers to the internal two-dimensional structure: it stores a hash, and for each hash, a (list of) key(s) corresponding to that hash. Storing a value alongside the key is a third "dimension".
Some of early Go's design decisions were kinda stupid, but they didn't screw that one up.
As others have said, the issue with Go parsing JSON is that Go doesn't handle unstructured data at all well, and most other languages consider JSON to be unstructured data. Go expects the JSON to be strongly typed and rigidly defined, mirroring a struct in the Go code that it can use as a receiver for the values.
There are techniques for handling this, but they're not obvious and usually learned by painful experience. This is not all Go's fault - there are too many endpoints out there that return wildly variable JSON depending on context.
The pain of dealing with JSON in Go is one of the primary reasons I stick mostly with nodejs for my api servers.
Unless you're dealing with JSON input that has missing fields, or unexpected fields, there is no pain. Go can natively turn a JSON payload into a struct as long as the payload's fields recursively match the struct's fields!
If, in any language, you're consuming or generating JSON that doesn't match a specific predetermined structure, you're yolo'ing it and all bets are off. Go makes this particualr footgun hard to do, while JS, Python, etc makes it the default.
Default footguns are a bad idea, not a good idea.
In $other_language you'll parse the JSON fine, but then smack into problems when the field you're expecting to be there isn't, or is in the wrong format, or the wrong type, etc.
In Go, as always, this is up front and explicit. You hit that problem when you parse the JSON, not later when you try to use the resulting data.
For example, this will parse just fine [0]:
type myvalue struct {
First int `json:"first"`
}
type myobj struct {
List []myvalue `json:"list"`
}
js := "{\"list\": [{\"second\": \"cde\"}]}"
var obj myobj
err := json.Unmarshal([]byte(js), &obj)
if err != nil {
return fmt.Errorf("Error unmarshalling: %+v", err)
}
fmt.Printf("The expected value was %+v", obj) //prints {List:[{First:0}]}
This is arguably worse than what you'd get in Python if you tried to access the key "first".One of the techniques for dealing with JSON in Go is to not try to parse the entire JSON in one go, but to parse it using smaller structs that only partially match the JSON. e.g. if you endpoint returns either an int or a string, depending on the result, a single struct won't match. But two structs, one with an int and one with a string - that will parse the value and then you can work out which one it was.
To me it looks like a footgun: if the parsing failed then an error should have been signalled. In this case, there is no error and you silently get the wrong value.
I do agree that there are good reasons on why this behaves the way it does, but I don't think the reason you cite is good. The implementation detail of generating a 0 value is not a good reason for why you'd implement JSON decoding like this.
Instead, the reason this is not a completely inane choice is that it is sometimes useful to simply not include keys that are meant to have a default value. This is a common practice in web APIs, to avoid excessive verbosity; and it is explicitly encoded in standards like OpenAPI (where you can specify whether a field of an object is required or not).
On the implementation side, I can then get away with always decoding to a single struct, I don't have to define specific structs for each field or combination of fields.
Ideally, this would have been an optional feature, where you could specify in the struct definition whether a fields is required or not (e.g. something like `json:"fieldName;required"` or `json:"fieldName;optional"`). Parsing would fail if any required field was not present in the JSON. However, this would have been more work on the Go team, and they generally prefer to implement something that works and be done with it, rather than working for all important cases.
Separately, ignoring extra fields in the JSON that don't match any fields in the struct is pretty useful for maintaining backwards compatibility. Adding extra fields should not generally break backwards compatibility.
> One of the techniques for dealing with JSON in Go is to not try to parse the entire JSON in one go, but to parse it using smaller structs that only partially match the JSON. e.g. if you endpoint returns either an int or a string, depending on the result, a single struct won't match. But two structs, one with an int and one with a string - that will parse the value and then you can work out which one it was.
I have no idea what you mean here. json.Unmarshal() is an all-or-nothing operation. Are you saying it's common practice to use json.Decoder instead?
No, I mean you create a struct that deals with only a part of the JSON, and do multiple calls to Unmarshal. Each struct gets either populated or left at its zero-value depending on what the json looks like. It's useful for parsing json data that has a variable schema depending on what the result was.
dataMap := make(map[string]any)
err = json.Unmarshal(bytes, &dataMap)https://github.com/kubernetes/apimachinery/blob/95b78024e3fe...
Ideally, such `nested` map helper functions should be part of the stdlib.