
本文详解如何在 Go 中正确解析含原始 XML 字符串字段的 JSON 响应,避免 invalid character '
本文详解如何在 go 中正确解析含原始 xml 字符串字段的 json 响应,避免 `invalid character 'json 解析器对转义与原始字符串的处理规则,并通过结构体标签、预处理或类型适配确保 xml 内容被原样保留为 `string`。
在 Go 开发中,常遇到一类典型混合格式场景:HTTP 接口返回标准 JSON,但其中某个字段(如 "ccda")的值是已序列化的 XML 字符串(即
? 错误根源分析
该错误通常由以下三种情况之一导致:
响应实际不是 JSON,而是纯 XML
服务端未按预期返回 Content-Type: application/json,而返回了 XML 响应(如 application/xml 或无明确类型)。Go 的 json.Unmarshal 尝试解析 XML 文本,自然失败。
✅ 验证方式:打印 resp.Header.Get("Content-Type") 和原始 body 前 200 字节,确认是否为-
JSON 字符串中的 XML 未被正确 JSON 转义
合法 JSON 要求双引号内所有特殊字符(如 ", , 等)必须转义。但 XML 字符串若含未转义的 、&,会导致 JSON 语法非法。例如:{ "ccda": "<clinicaldocument>...</clinicaldocument>" } // ❌ 非法 JSON(..." } // ✅ 合法( 被转义)实际中,部分老旧 API 可能忽略此规范,直接拼接 XML 字符串入 JSON,造成解析崩溃。
客户端未设置 Accept Header,服务端降级返回 XML
如知识库答案所提示:若请求未显式声明 Accept: application/json,某些后端会默认返回 XML 格式响应。这是最隐蔽也最常见的原因。
✅ 正确解决方案(三步走)
步骤 1:先验证响应内容类型与原始字节
body, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
log.Fatal("read body failed:", err)
}
contentType := resp.Header.Get("Content-Type")
log.Printf("Content-Type: %s", contentType)
log.Printf("Raw body (first 200 chars): %s", string(body[:min(len(body), 200)]))
// 若发现 Content-Type 是 application/xml 或 body 以 <?xml 开头,则直接跳过 JSON 解析
if strings.Contains(contentType, "xml") || bytes.HasPrefix(body, []byte("<?xml")) {
// 直接当作 XML 处理
xmlData := body
// ... 后续发送至其他服务
return
}步骤 2:定义结构体时使用 json.RawMessage(推荐)
当需完全跳过 JSON 解析器对某字段的进一步解析(即把 "ccda" 的值原样存为 []byte),应使用 json.RawMessage 类型——它告诉 json 包:“此处数据保持原始字节,不要尝试解码”:
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
type Ccda struct {
CCDA json.RawMessage `json:"ccda"` // ✅ 关键:不解析,只拷贝原始 JSON 字符串值
}
var ccr []Ccda
err := json.Unmarshal(body, &ccr)
if err != nil {
if syntaxErr, ok := err.(*json.SyntaxError); ok {
log.Printf("JSON syntax error at offset %d: %s",
syntaxErr.Offset,
string(body[max(0, int(syntaxErr.Offset)-20):min(len(body), int(syntaxErr.Offset)+20)]))
}
log.Fatal("Unmarshal failed:", err)
}
// 提取 XML 字符串(自动处理 JSON 双引号内的转义)
xmlStr := string(ccr[0].CCDA) // 例如:"<?xml version="1.0"?>
<clinicaldocument>..."</clinicaldocument>? json.RawMessage 是 []byte 的别名,Unmarshal 会将其设为 JSON 字符串值(不含外层引号)的原始字节,包括已转义的 "、 等。后续可直接 string() 转换为 Go 字符串。
步骤 3:若需进一步健壮性 —— 预处理修复常见转义缺陷
某些 API 返回的 JSON 中,XML 内容的 &、 未被转义,但又非纯 XML 响应。此时可做轻量预处理(仅当确认问题存在时启用):
// 仅在 JSON 解析失败且疑似 XML 特征时尝试修复(谨慎使用!)
fixedBody := bytes.ReplaceAll(body, []byte(""), []byte("\u003e"))
fixedBody = bytes.ReplaceAll(fixedBody, []byte("&"), []byte("\u0026"))
err := json.Unmarshal(fixedBody, &ccr) // 再次尝试⚠️ 注意:此方案属兜底策略,应优先推动服务端修复 JSON 合规性。
? 关键注意事项
- 永远不要依赖 ioutil.ReadAll(已弃用):使用 io.ReadAll 替代(Go 1.16+)。
- json.RawMessage 不等于 string:它存储的是 JSON 字符串值的原始字节(不含引号),因此 string(raw) 即为所需 XML 字符串。
- 避免 map[string]interface{} 的陷阱:虽然它能绕过结构体定义,但 ccda 字段会被解析为 string 类型,若 JSON 本身非法,仍会报错;且无法利用 json.RawMessage 的零解析优势。
- 生产环境务必加超时与重试:HTTP 客户端应配置 http.Client.Timeout,并考虑对 JSON 解析失败做降级日志与告警。
✅ 最终推荐代码模板
func fetchAndExtractCCDA(url string) (string, error) {
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
// Step 1: 检查是否为 XML 响应
if strings.Contains(resp.Header.Get("Content-Type"), "xml") ||
bytes.HasPrefix(body, []byte("<?xml ")) ||
bytes.HasPrefix(body, []byte("<ClinicalDocument")) {
return string(body), nil // 直接返回原始 XML
}
// Step 2: 尝试 JSON 解析
type Ccda struct {
CCDA json.RawMessage `json:"ccda"`
}
var ccr []Ccda
if err := json.Unmarshal(body, &ccr); err != nil {
return "", fmt.Errorf("failed to unmarshal JSON: %w", err)
}
if len(ccr) == 0 {
return "", errors.New("empty response array")
}
return string(ccr[0].CCDA), nil
}通过以上方法,你将彻底规避 invalid character '










