将 map[string]string 编组为 json return 会出错吗?
Can marshalling a map[string]string to json return an error?
假设我有以下代码:
m := map[string]string{}
//... do stuff to the map
b, err := json.Marshal(m)
在这种情况下,json.Marshal
调用是否会 return 出错?
我想知道,部分是出于好奇,部分是为了考虑我是否需要担心错误检查。
因为任何有效的string
值都是一个有效的键,也是JSON中的一个有效值(详情见Which characters are valid/invalid in a JSON key name?),理论上它不会return任何错误。
如果发生内存不足错误,json.Marshal()
不会 return,您的应用程序将终止并显示错误代码。
由于 Go 将 string
值存储为其 UTF-8 编码字节序列,因此存在无效 UTF-8 编码字符串内容的问题。这也不会导致任何错误,因为 Go 将用 Unicode 替换字符 U+FFFD 替换无效代码点,如本例所示:
m := map[string]string{"\xff": "a"}
data, err := json.Marshal(m)
fmt.Println(string(data), err)
输出(在 Go Playground 上尝试):
{"\ufffd":"a"} <nil>
此行为记录在 json.Marshal()
:
String values encode as JSON strings coerced to valid UTF-8, replacing invalid bytes with the Unicode replacement rune.
封送 map[string]string
可能永远不会 return 错误,不过,您应该始终检查 returned 错误,除非文档明确指出 returned error
总是 nil
(json.Marshal()
的文档没有记录这种行为)。这种罕见的例子是 rand.Read()
,它记录了 "it always returns len(p) and a nil error".
而且也有可能是标准库有错误,所以即使json
包的实现也未必打算到return封送 map[string]string
时的任何错误,一个错误可能会导致它仍然 return 一个非 nil
错误。
另见相关问题:
并发地图修改
为了完整起见,让我们讨论另一个可能导致 json.Marshal()
在传递 map[string]string
时失败的问题。
Go 1.6 added a lightweight concurrent misuse of maps detection to the runtime, you can read more about it here:
这意味着 Go 运行时可以检测一个映射是否在一个 goroutine 中被读取或修改,并且它也被另一个 goroutine 同时修改,没有同步。
所以这里的场景是我们将 map[string]string
传递给 json.Marshal()
。为了对其进行编组,json
包显然必须遍历地图的 key-values。如果我们同时修改地图,那将导致失败。
这是一个激发它的示例代码(循环是为了增加并发修改的可能性,否则我们将落入 goroutine 调度程序的手中):
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)
}
}
另请注意,在这种情况下 json.Marshal()
也不会 return(就像 out-of-memory 错误的情况一样),相反,运行时会故意使您的应用程序崩溃。输出将是:
fatal error: concurrent map iteration and map write
假设我有以下代码:
m := map[string]string{}
//... do stuff to the map
b, err := json.Marshal(m)
在这种情况下,json.Marshal
调用是否会 return 出错?
我想知道,部分是出于好奇,部分是为了考虑我是否需要担心错误检查。
因为任何有效的string
值都是一个有效的键,也是JSON中的一个有效值(详情见Which characters are valid/invalid in a JSON key name?),理论上它不会return任何错误。
如果发生内存不足错误,json.Marshal()
不会 return,您的应用程序将终止并显示错误代码。
由于 Go 将 string
值存储为其 UTF-8 编码字节序列,因此存在无效 UTF-8 编码字符串内容的问题。这也不会导致任何错误,因为 Go 将用 Unicode 替换字符 U+FFFD 替换无效代码点,如本例所示:
m := map[string]string{"\xff": "a"}
data, err := json.Marshal(m)
fmt.Println(string(data), err)
输出(在 Go Playground 上尝试):
{"\ufffd":"a"} <nil>
此行为记录在 json.Marshal()
:
String values encode as JSON strings coerced to valid UTF-8, replacing invalid bytes with the Unicode replacement rune.
封送 map[string]string
可能永远不会 return 错误,不过,您应该始终检查 returned 错误,除非文档明确指出 returned error
总是 nil
(json.Marshal()
的文档没有记录这种行为)。这种罕见的例子是 rand.Read()
,它记录了 "it always returns len(p) and a nil error".
而且也有可能是标准库有错误,所以即使json
包的实现也未必打算到return封送 map[string]string
时的任何错误,一个错误可能会导致它仍然 return 一个非 nil
错误。
另见相关问题:
并发地图修改
为了完整起见,让我们讨论另一个可能导致 json.Marshal()
在传递 map[string]string
时失败的问题。
Go 1.6 added a lightweight concurrent misuse of maps detection to the runtime, you can read more about it here:
这意味着 Go 运行时可以检测一个映射是否在一个 goroutine 中被读取或修改,并且它也被另一个 goroutine 同时修改,没有同步。
所以这里的场景是我们将 map[string]string
传递给 json.Marshal()
。为了对其进行编组,json
包显然必须遍历地图的 key-values。如果我们同时修改地图,那将导致失败。
这是一个激发它的示例代码(循环是为了增加并发修改的可能性,否则我们将落入 goroutine 调度程序的手中):
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)
}
}
另请注意,在这种情况下 json.Marshal()
也不会 return(就像 out-of-memory 错误的情况一样),相反,运行时会故意使您的应用程序崩溃。输出将是:
fatal error: concurrent map iteration and map write