Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can marshalling a map[string]string to json return an error?

Say I have the following code:

m := map[string]string{}
//... do stuff to the map
b, err := json.Marshal(m)

Is there any way in this case that the json.Marshal call would return an error?

I'm wondering partly out of curiosity and partly to consider whether I need to worry about that error check at all.

like image 371
DMac the Destroyer Avatar asked Mar 12 '18 15:03

DMac the Destroyer


People also ask

How to convert map string interface to JSON in golang?

To convert Map object to JSON string in Go language, import the package encode/json, call json. Marshall() function and pass the map object as argument to the function. The function returns json string and an error object.

What is marshal in JSON?

JSON has 3 basic types: booleans, numbers, strings, combined using arrays and objects to build complex structures. Go's terminology calls marshal the process of generating a JSON string from a data structure, and unmarshal the act of parsing JSON to a data structure.


1 Answers

Since any valid string value is a valid key and also a valid value in JSON (for details see Which characters are valid/invalid in a JSON key name?), theoretically it won't return any errors.

If an out of memory error would occur, json.Marshal() would not return, your app would terminate with an error code.

Since Go stores string values as their UTF-8 encoded byte sequences, there is the question of invalid UTF-8 encoded string content. This also won't result in any errors, as Go will substitute invalid code points with the Unicode replacement character U+FFFD, like in this example:

m := map[string]string{"\xff": "a"}
data, err := json.Marshal(m)
fmt.Println(string(data), err)

Output (try it on the Go Playground):

{"\ufffd":"a"} <nil>

This behavior is documented at json.Marshal():

String values encode as JSON strings coerced to valid UTF-8, replacing invalid bytes with the Unicode replacement rune.

It may be that marshaling a map[string]string will never return an error, still, you should always check returned errors unless the doc explicitly states that the returned error is always nil (the doc of json.Marshal() does not document such behavior). Such rare example is rand.Read() which documents that "it always returns len(p) and a nil error".

And there is also the possibility that the standard library has errors, so even though the implementation of the json package may not intend to return any error when marshaling a map[string]string, a bug may cause it to still return a non-nil error.

Also see related question: Go : When will json.Unmarshal to struct return error?

Concurrent map modification

For completeness, let's discuss another issue that might cause json.Marshal() to fail when a map[string]string is passed to it.

Go 1.6 added a lightweight concurrent misuse of maps detection to the runtime, you can read more about it here: How to recover from concurrent map writes?

This means that the Go runtime may detect if a map is read or modified in a goroutine, and it is also modified by another goroutine, concurrently, without synchronization.

So the scenario here is that we pass a map[string]string to json.Marshal(). And for it to be marshaled, the json package has to iterate over the key-values of the map obviously. If we modify the map concurrently, that will result in a fail.

Here is a sample code that provokes it (the loop is there to increase the likeliness of the concurrent modification, else we would be in the hands of the goroutine scheduler):

m := map[string]string{"\xff": "a"}

go func() {
    for i := 0; i < 10000; i++ {
        m["x"] = "b"
    }
}()

for i := 0; i < 10000; i++ {
    if _, err := json.Marshal(m); err != nil {
        panic(err)
    }
}

Also note that in this case json.Marshal() will also not return (just like with the case of the out-of-memory error), instead the runtime will crash your app, intentionally. Output will be:

fatal error: concurrent map iteration and map write
like image 62
icza Avatar answered Oct 21 '22 21:10

icza