如何连续附加到 Go 中的 JSON 文件?

How to append consecutively to a JSON file in Go?

我想知道如何在 Go 中将 连续 写入同一个文件?我必须使用 os.WriteAt() 吗?

JSON 基本上只是一个充满结构的数组:

[
    {
        "Id": "2817293",
        "Data": "XXXXXXX"
    },
    {
        "Id": "2817438",
        "Data": "XXXXXXX"
    }
    ...
]

我想要连续向它添加正确的数据,即在关闭文件之前多次附加到 JSON 数组。 我要写入文件的数据是所述结构的一部分:

dataToWrite := []struct{
    Id   string
    Data string
}{}

在 Go 中连续写入 JSON 数组的正确方法是什么?

我目前的方法是在 JSON 文件中创建多个切片,因此这不是我想要的。写入过程(位于for循环中)如下所示:

...
            // Read current state of file
            data := []byte{}
            f.Read(data)

            // Write current state to slice
            curr := []Result{}
            json.Unmarshal(data, &curr)

            // Append data to the created slice
            curr = append(curr, *initArr...)
            JSON, _ := JSONMarshal(curr)

            // Empty data container
            initArr = &[]Result{}

            // Write
            _, err := f.Write(JSON)
            if err != nil {
                log.Fatal(err)
            }
...

如果您不关心现有文件,您可以像@redblue 提到的那样在整个切片上使用 Encoder.Encode

如果您有想要附加到的现有文件,最简单的方法是执行您在编辑中显示的内容:UnmarshalDecoder.Decoder 将整个文件分成一部分结构,将新结构附加到切片,并使用 MarshalEncoder.Encode.

re-decode 全部

如果你的数据量很大,可以考虑使用JSON Lines来避免尾随,]的问题,写一个JSON每行一个对象。或者您可以使用常规 JSON,从文件末尾向后查找,以便覆盖最后的 ],然后写入 ,,新的 JSON-encoded 结构,最后是 ] 使文件再次成为有效的 JSON 数组。

所以这在一定程度上取决于您的用例和您采用哪种方法的数据大小。

将开头[写入文件。在文件上创建一个编码器。遍历切片和每个切片的元素。如果它不是第一个切片元素,请写一个逗号。使用编码器对每个切片元素进行编码。写结尾 ].

_, err := f.WriteString("[")
if err != nil {
    log.Fatal(err)
}

e := json.NewEncoder(f)
first := true
for i := 0; i < 10; i++ {

    // Create dummy slice data for this iteration.

    dataToWrite := []struct {
        Id   string
        Data string
    }{
        {fmt.Sprintf("id%d.1", i), fmt.Sprintf("data%d.1", i)},
        {fmt.Sprintf("id%d.2", i), fmt.Sprintf("data%d.2", i)},
    }

    // Encode each slice element to the file
    for _, v := range dataToWrite {

        // Write comma separator if not the first.
        if !first {
            _, err := f.WriteString(",\n")
            if err != nil {
                log.Fatal(err)
            }
        }
        first = false

        err := e.Encode(v)
        if err != nil {
            log.Fatal(err)
        }
    }
}
_, err = f.WriteString("]")
if err != nil {
    log.Fatal(err)
}

https://go.dev/play/p/Z-T1nxRIaqL


如果将所有切片元素保存在内存中是合理的,则通过在单个批次中对所有数据进行编码来简化代码:

type Item struct {
    Id   string
    Data string
}

// Collect all items to write in this slice.
var result []Item

for i := 0; i < 10; i++ {
    // Generate slice for this iteration.
    dataToWrite := []Item{
        {fmt.Sprintf("id%d.1", i), fmt.Sprintf("data%d.1", i)},
        {fmt.Sprintf("id%d.2", i), fmt.Sprintf("data%d.2", i)},
    }

    // Append slice generated in this iteration to the result.
    result = append(result, dataToWrite...)
}

// Write the result to the file.
err := json.NewEncoder(f).Encode(result)
if err != nil {
    log.Fatal(err)
}

https://go.dev/play/p/01xmVZg7ePc

通知
如果您关心现有文件的内容,则此答案是 solutionworkaround
这意味着它允许您附加到由您的 API.

创建的 existing json 文件

显然这个只对相同结构

的数组有效

实际工作json格式:

[
object,
...
object,
]

写入文件时,不要写入 []。 只需附加到写入序列化 json 对象的文件并附加一个 ,

实际文件内容:

object,
...
object,

最后在读取文件时添加 [ 并添加 ]

通过这种方式,您可以 write 从多个来源访问文件并且仍然具有有效的 JSON

同时加载文件并为您的 json 处理器提供有效输入。

我们这样写日志文件,并通过 REST 调用提供有效的 json,然后对其进行处理(例如通过 JavaScript Grid