本文详解如何将 PostgreSQL 中存储的双重转义 JSON 字符串(如 '{"{\"hello\":\"world\"}"}')在 Go 中安全解析为结构体或动态映射,涵盖 json.RawMessage、嵌套反序列化、结构体定义及常见陷阱。
本文详解如何将 postgresql 中存储的双重转义 json 字符串(如 `'{"{\"hello\":\"world\"}"}'`)在 go 中安全解析为结构体或动态映射,涵盖 `json.rawmessage`、嵌套反序列化、结构体定义及常见陷阱。
在 PostgreSQL 中,若某字段(如 json_data TEXT 或 jsonb)实际存的是被双重 JSON 编码的字符串——例如原始值是 {"hello":"world"},但数据库中却以字符串形式存储为 "{"\"hello\":\"world\"}"(即外层是 JSON 字符串,内层才是有效对象),那么直接用 json.Unmarshal 解析到结构体会失败:Go 会尝试将整个字符串字面量(而非其内容)映射到 struct 字段,导致 json: cannot unmarshal string into Go struct field ... 错误。
正确做法是分两步解析:先将外层字符串解包为 string,再对其中内容做二次 JSON 解析。推荐使用 json.RawMessage 避免重复拷贝,并提升类型安全性:
type Record struct {
ID int `db:"id"`
JSONData json.RawMessage `db:"json_data"` // 保留原始字节,延迟解析
}
// 查询后解析
var record Record
err := db.Get(&record, "SELECT id, json_data FROM table WHERE id = $1", 1)
if err != nil {
log.Fatal(err)
}
// 第一步:将 RawMessage 转为普通字符串(自动处理引号与转义)
var innerJSON string
if err := json.Unmarshal(record.JSONData, &innerJSON); err != nil {
log.Fatal("failed to unmarshal outer string:", err)
}
// 第二步:解析 innerJSON 这个真正的 JSON 内容
var payload map[string]interface{}
if err := json.Unmarshal([]byte(innerJSON), &payload); err != nil {
log.Fatal("failed to unmarshal inner JSON:", err)
}
fmt.Printf("Parsed: %+v\n", payload) // map[hello:world]
若 JSON 结构固定(如始终包含 hello、timestamp、count 等字段),优先定义结构体而非泛型 map[string]interface{},以获得编译期检查和字段语义:
type Payload struct {
Hello string `json:"hello"`
Timestamp time.Time `json:"timestamp,omitempty"`
Count int `json:"count"`
}
// 复用上面的 innerJSON
var payload Payload
if err := json.Unmarshal([]byte(innerJSON), &payload); err != nil {
log.Fatal("unmarshal to struct failed:", err)
}
fmt.Println(payload.Hello) // "world"
⚠️ 注意事项:
- 不要直接用 string 类型接收 PostgreSQL 的 JSON 字段(如 db:"json_data" string),这会丢失转义信息,导致 innerJSON 变成带多余反斜杠的乱码;
- 使用 json.RawMessage 是标准实践,它底层是 []byte,零拷贝且可多次解析;
- 若字段可能为空或格式非法,务必添加 omitempty 标签并检查 err,避免 panic;
- 对于高度动态的场景(如用户自定义 schema),可用 map[string]json.RawMessage 实现部分懒解析,按需展开子字段。
总结:面对 PostgreSQL 中“字符串化的 JSON”,Go 的最佳路径是「json.RawMessage → string → 二次 json.Unmarshal」;结构体适用于已知 schema,map[string]interface{} 适用于完全动态结构——二者可结合使用,兼顾类型安全与灵活性。











