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

后端 未结 1 799
后悔当初
后悔当初 2021-01-19 01:57

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 t

相关标签:
1条回答
  • 2021-01-19 02:44

    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
    
    0 讨论(0)
提交回复
热议问题