Golang中JSON解码输入缓冲功能的提案讨论

Golang中JSON解码输入缓冲功能的提案讨论 我惊讶地发现,当使用 Decode (encoding/json) 从 io.Reader 解析 JSON 到结构体时,整个内容都被存储在内存中,而不是像大多数 io.Reader 的使用者那样以缓冲的方式从 Reader 中 Read 出来。

我正在读取一些我无法控制的 JSON 对象,因此我期望能够通过将内容缓冲到解码器来防止内存不足的问题。

查阅资料后,我发现解码器显然不是以这种方式工作的,而是用于处理通过 Reader 传入的许多独立对象。

问题 1: 我如何控制此过程的内存使用?如果无法限制内存使用,我如何安全地运行这些处理器,或者设置一个安全的并发级别?

示例

假设我想填充以下结构体:

type user struct {
    Name string
}

而我得到的 JSON 是:

{"Junk": ["...large", "nested array", "of great size"], "Name": "jojo"}

难道不应该读取到标记 Junk,发现它不是必需的,然后跳过它的值(下一个 Delim 逗号)吗?

目前,Decode 函数在解析所需键值之前,会反复调整其内部缓冲区的大小以完全容纳原始字节。

问题 2: 这值得在 https://github.com/golang/go/issues 提交一个功能提案吗?或者是否有可能以一种我遗漏的内存安全方式来实现这一点?


更多关于Golang中JSON解码输入缓冲功能的提案讨论的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中JSON解码输入缓冲功能的提案讨论的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go的encoding/json包中,Decoder确实会将整个JSON对象读入内存进行解析。这是因为它需要完整的JSON数据来确保正确的语法分析和结构映射。对于大型JSON对象,这可能导致高内存使用。

问题1:控制内存使用的方法

方案1:使用json.RawMessage延迟解析

type user struct {
    Name string
    Junk json.RawMessage `json:"Junk,omitempty"`
}

func processLargeJSON(r io.Reader) error {
    decoder := json.NewDecoder(r)
    for {
        var u user
        if err := decoder.Decode(&u); err != nil {
            if err == io.EOF {
                break
            }
            return err
        }
        // 只处理Name字段,Junk字段保持原始JSON不解析
        fmt.Printf("User: %s\n", u.Name)
    }
    return nil
}

方案2:流式处理使用Token API

func streamJSON(r io.Reader) error {
    decoder := json.NewDecoder(r)
    
    // 进入对象
    t, err := decoder.Token()
    if err != nil {
        return err
    }
    if delim, ok := t.(json.Delim); !ok || delim != '{' {
        return fmt.Errorf("expected object")
    }
    
    // 遍历对象键值对
    for decoder.More() {
        // 读取键
        key, err := decoder.Token()
        if err != nil {
            return err
        }
        
        // 如果是Name字段,直接解析值
        if key == "Name" {
            var name string
            if err := decoder.Decode(&name); err != nil {
                return err
            }
            fmt.Printf("Name: %s\n", name)
        } else {
            // 跳过其他字段的值
            var skip interface{}
            if err := decoder.Decode(&skip); err != nil {
                return err
            }
        }
    }
    
    // 结束对象
    t, err = decoder.Token()
    if err != nil {
        return err
    }
    return nil
}

方案3:限制并发处理

type job struct {
    data []byte
    user user
}

func processWithConcurrencyLimit(r io.Reader, maxConcurrent int) error {
    decoder := json.NewDecoder(r)
    sem := make(chan struct{}, maxConcurrent)
    var wg sync.WaitGroup
    var mu sync.Mutex
    
    for {
        var raw json.RawMessage
        if err := decoder.Decode(&raw); err != nil {
            if err == io.EOF {
                break
            }
            return err
        }
        
        wg.Add(1)
        go func(data []byte) {
            defer wg.Done()
            sem <- struct{}{}
            defer func() { <-sem }()
            
            var u user
            if err := json.Unmarshal(data, &u); err != nil {
                mu.Lock()
                fmt.Printf("Error: %v\n", err)
                mu.Unlock()
                return
            }
            
            mu.Lock()
            fmt.Printf("Processed: %s\n", u.Name)
            mu.Unlock()
        }(raw)
    }
    
    wg.Wait()
    return nil
}

问题2:关于功能提案

当前encoding/json的设计确实需要整个对象在内存中。提交功能提案是合理的,但需要考虑以下技术细节:

现有替代方案示例

// 使用第三方库如json-iterator/go进行流式解析
import "github.com/json-iterator/go"

func parseWithJsonIterator(r io.Reader) error {
    iter := jsoniter.Parse(jsoniter.ConfigDefault, r, 4096) // 可配置缓冲区大小
    for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
        if field == "Name" {
            name := iter.ReadString()
            fmt.Printf("Name: %s\n", name)
        } else {
            iter.Skip()
        }
    }
    return iter.Error
}

可能的API扩展示例

// 假设的未来API
type SelectiveDecoder struct {
    *json.Decoder
    fields map[string]bool
}

func (sd *SelectiveDecoder) DecodeSelective(v interface{}) error {
    // 实现选择性字段解析逻辑
    // 只解析需要的字段,跳过其他字段
}

当前情况下,最实用的解决方案是结合使用json.RawMessage和流式处理API。对于超大JSON文档,考虑使用专门的流式JSON解析器或实现自定义解析逻辑。

回到顶部