如何在Go中连续追加JSON文件?

huangapple go评论88阅读模式
英文:

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文件中创建多个切片,这不是我想要的结果。写入过程(位于一个循环中)如下所示:

...
			// 读取文件的当前状态
			data := []byte{}
			f.Read(data)

			// 将当前状态写入切片
			curr := []Result{}
			json.Unmarshal(data, &curr)

			// 将数据追加到已创建的切片中
			curr = append(curr, *initArr...)
			JSON, _ := JSONMarshal(curr)

			// 清空数据容器
			initArr = &[]Result{}

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

I wonder how can I write consecutively to the same file in Go? Do I have to use os.WriteAt()?

The JSON is basically just an array filled with structs:

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

I want right data to it consecutively i.e. append to that JSON array more than once before closing the file.
The data I want to write to the file is a slice of said structs:

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

What is the proper way to write consecutively to a JSON array in Go?

My current approach creates multiple slices in the JSON file and thus is not what I want. The write process (lying in a for loop) looks like this:

...
			// 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)
			}
...

答案1

得分: 2

写入文件时,先写入开头的[符号。在文件上创建一个编码器。循环遍历切片和每个切片的元素。如果不是第一个切片元素,则写入逗号。使用编码器对每个切片元素进行编码。最后写入结尾的]符号。

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

e := json.NewEncoder(f)
first := true
for i := 0; i < 10; i++ {
    // 为此迭代创建虚拟切片数据。
    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)},
    }

    // 将每个切片元素编码到文件中
    for _, v := range dataToWrite {
        // 如果不是第一个元素,则写入逗号分隔符。
        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)
}

如果合理的话,可以将所有切片元素都保存在内存中,然后通过一次性编码所有数据来简化代码:

type Item struct {
    Id   string
    Data string
}

// 收集要写入的所有项。
var result []Item

for i := 0; i < 10; i++ {
    // 为此迭代生成切片。
    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)},
    }

    // 将此迭代生成的切片附加到结果中。
    result = append(result, dataToWrite...)
}

// 将结果写入文件。
err := json.NewEncoder(f).Encode(result)
if err != nil {
    log.Fatal(err)
}

请注意,以上代码示例中的flog变量需要根据实际情况进行定义和处理。

英文:

Write the opening [ to the file. Create an encoder on the file. Loop over slices and the elements of each slice. Write a comma if it's not the first slice element. Encode each slice element with the encoder. Write the closing ].

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

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

	// Create dummy slice data for this iteration.

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

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

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

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

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


If it's reasonable to hold all of the slice elements in memory, then simplify the code by encoding all of the data in a single batch:

type Item struct {
	Id   string
	Data string
}

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

for i := 0; i &lt; 10; i++ {
	// Generate slice for this iteration.
	dataToWrite := []Item{
		{fmt.Sprintf(&quot;id%d.1&quot;, i), fmt.Sprintf(&quot;data%d.1&quot;, i)},
		{fmt.Sprintf(&quot;id%d.2&quot;, i), fmt.Sprintf(&quot;data%d.2&quot;, 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

答案2

得分: 1

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

如果您有一个现有的文件要追加,最简单的方法是按照您在编辑中展示的方式:将整个文件UnmarshalDecoder.Decoder为一个结构体切片,将新的结构体追加到切片中,然后使用MarshalEncoder.Encode重新编码整个切片。

如果您有大量的数据,您可能希望考虑使用JSON Lines来避免尾随的,]问题,并且每行写入一个JSON对象。或者您可以使用常规的JSON,从文件末尾回溯,这样您就可以覆盖最后的],然后写入一个,,新的JSON编码结构体,最后再写入一个],使文件再次成为有效的JSON数组。

因此,取决于您的用例和数据大小,您可以选择哪种方法。

英文:

If you don't care about the existing file you can just use Encoder.Encode on the whole slice as @redblue mentioned.

If you have an existing file you want to append to, the simplest way is to do what you've shown in your edit: Unmarshal or Decoder.Decoder the whole file into a slice of structs, append the new struct to the slice, and re-decode the whole lot using Marshal or Encoder.Encode.

If you have a large amount of data, you may want to consider using JSON Lines to avoid the trailing , and ] issue, and write one JSON object per line. Or you could use regular JSON, seek back from the end of the file so you're writing over the final ], then write a ,, the new JSON-encoded struct, and finally a ] to make the file a valid JSON array again.

So it depends on a bit on your use case and the data size which approach you take.

答案3

得分: 0

注意
这个答案是一个关于如何处理现有文件内容的“解决方案”或“解决方法”!
这意味着它允许你向由你的API创建的“json”文件中追加内容。

显然,这仅适用于具有相同结构的数组。

实际工作的json格式:

[
object,
...
object,
]

在写入文件时,不要写入[]
只需将序列化的json对象追加到文件中,并添加一个逗号。

实际文件内容:

object,
...
object,

最后,在读取文件时,在前面添加[,在后面添加]

这样,你可以从多个来源向文件中写入内容,并仍然得到有效的JSON。

同时,加载文件时,你可以为你的JSON处理器提供有效的输入。

我们就是这样编写我们的日志文件,并通过REST调用提供一个有效的JSON,然后通过JavaScript Grid进行处理。

英文:

NOTICE
This Answer is a solution or workaround if you care about the content of an existing file!
This means it allows you to append to an existing json file, created by your API.

Obviously this only works for arrays of same structs

actual working json format:

[
object,
...
object,
]

When writing to a file, do NOT write [ and ].
Just append to the file writing your serialized json object and append a ,

actual file content:

object,
...
object,

Finally when reading the file prepend [ and append ]

This way you can write to the file from multiple sources and still have valid JSON

Also load the file and have a valid input for your json-processor.

We write our logfiles like this and provide a vaild json via REST calls, which is then processed (for example by a JavaScript Grid)

huangapple
  • 本文由 发表于 2022年6月1日 11:12:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/72456088.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定