在Go语言的标准库中,使用encoding/json包解析JSON时,如果出现重复键,后出现的键值对会覆盖先出现的内容,且不会返回任何错误提示,这在某些需要严格校验输入的场景下会带来隐患。因此我们需要额外实现逻辑来检测JSON中的重复键。

基于标准库自定义解析器实现检测
我们可以通过自定义json.Decoder的行为,在解析过程中记录已经出现过的键,从而判断是否存在重复。核心思路是实现io.Reader接口包装原始输入,在解析每个键值对时做校验。
实现步骤
- 定义一个结构体,内部维护已出现键的集合和原始输入流
- 重写Read方法,在读取过程中解析JSON的键并校验重复
- 使用自定义的Reader作为json.Decoder的输入源
代码示例
package main
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
)
// 自定义Reader,用于检测JSON重复键
type duplicateKeyDetector struct {
reader io.Reader
scanner *bufio.Scanner
keys map[string]bool
inString bool
escape bool
keyBuffer bytes.Buffer
parsingKey bool
}
func newDuplicateKeyDetector(r io.Reader) *duplicateKeyDetector {
return &duplicateKeyDetector{
reader: r,
scanner: bufio.NewScanner(r),
keys: make(map[string]bool),
}
}
// 简化版解析逻辑,仅用于演示重复键检测核心思路
func (d *duplicateKeyDetector) detect() (map[string]interface{}, error) {
// 重置scanner,因为之前可能已经读取过部分内容
d.scanner = bufio.NewScanner(d.reader)
d.scanner.Split(bufio.ScanBytes)
d.keys = make(map[string]bool)
d.keyBuffer.Reset()
d.parsingKey = false
d.inString = false
d.escape = false
result := make(map[string]interface{})
for d.scanner.Scan() {
b := d.scanner.Bytes()[0]
// 处理字符串转义
if d.escape {
d.escape = false
if d.parsingKey {
d.keyBuffer.WriteByte(b)
}
continue
}
if b == '\' {
d.escape = true
if d.parsingKey {
d.keyBuffer.WriteByte(b)
}
continue
}
// 处理字符串开始和结束
if b == '"' {
d.inString = !d.inString
if d.parsingKey {
d.keyBuffer.WriteByte(b)
}
// 字符串结束,判断是否是键
if !d.inString && d.parsingKey {
key := d.keyBuffer.String()
// 去除键两端的引号
key = strings.Trim(key, """)
if _, exists := d.keys[key]; exists {
return nil, errors.New("检测到重复键: " + key)
}
d.keys[key] = true
d.parsingKey = false
d.keyBuffer.Reset()
}
continue
}
// 处理键的起始
if !d.inString && b == ':' {
d.parsingKey = true
continue
}
// 收集键的字符
if d.parsingKey && d.inString {
d.keyBuffer.WriteByte(b)
}
}
// 使用标准库解析完整JSON
d.reader = strings.NewReader(fmt.Sprint(d.scanner.Text()))
var finalResult map[string]interface{}
decoder := json.NewDecoder(strings.NewReader(fmt.Sprintf("{%s}", d.keyBuffer.String())))
// 重新读取原始完整输入解析
originalData, _ := io.ReadAll(d.reader)
decoder = json.NewDecoder(bytes.NewReader(originalData))
if err := decoder.Decode(&finalResult); err != nil {
return nil, err
}
return finalResult, nil
}
func main() {
// 测试包含重复键的JSON
jsonInput := `{"name": "test", "age": 18, "name": "repeat"}`
detector := newDuplicateKeyDetector(strings.NewReader(jsonInput))
_, err := detector.detect()
if err != nil {
fmt.Println("检测结果:", err)
}
// 测试无重复键的JSON
jsonInput2 := `{"name": "test", "age": 18, "gender": "male"}`
detector2 := newDuplicateKeyDetector(strings.NewReader(jsonInput2))
result, err := detector2.detect()
if err != nil {
fmt.Println("检测结果:", err)
} else {
fmt.Println("解析结果:", result)
}
}
使用第三方库简化实现
如果不想自己实现复杂的解析逻辑,可以使用第三方库来快速实现重复键检测。常用的库如github.com/icza/dyno,它提供了更灵活的JSON解析能力,支持检测重复键。
安装依赖
go get github.com/icza/dyno
代码示例
package main
import (
"fmt"
"github.com/icza/dyno"
)
func main() {
// 包含重复键的JSON
jsonStr := `{"a": 1, "b": 2, "a": 3}`
// 解析时检测重复键
v, err := dyno.NewJSONParser(strings.NewReader(jsonStr)).Parse()
if err != nil {
fmt.Println("解析错误:", err)
return
}
// 检查是否存在重复键
if dupErr := dyno.CheckDuplicateKeys(v); dupErr != nil {
fmt.Println("检测到重复键:", dupErr)
} else {
fmt.Println("无重复键,解析结果:", v)
}
}
两种方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 自定义标准库实现 | 无额外依赖,可控性高 | 实现复杂度高,需要处理各种JSON语法边界情况 |
| 第三方库实现 | 实现简单,开箱即用 | 需要引入外部依赖,可能存在版本兼容问题 |
注意事项
- 自定义解析逻辑时,需要完整处理JSON的各种语法,比如嵌套对象、数组、转义字符、Unicode字符等,否则可能出现漏检或误检
- 如果JSON输入来自不可信来源,检测重复键的同时也需要做其他安全校验,避免恶意输入导致解析异常
- 部分场景下重复键可能是允许的,需要根据实际业务需求决定是否开启检测逻辑
GoJSON重复键检测json_Decoder修改时间:2026-07-13 11:12:34