C#应用JSON数据源CRUD及写入:如何避免全文件重写?
关于JSON文件局部写入与C# CRUD操作的解决方案
嘿,我来帮你理清这个JSON文件操作的困惑!咱们先逐个解决你的问题:
问题1:能否在不重写整个文件的情况下写入JSON数据文件?
答案是分场景可行,但有很大局限性:
- 如果你的JSON根结构是数组,且仅需要在数组末尾追加元素,那可以通过定位文件指针、修改末尾内容实现局部写入(不用全量加载)。但这种方式要求文件格式严格规范,且无法处理中间元素的修改或删除。
- 对于其他场景(比如修改数组中间元素、更新嵌套对象属性),由于JSON是结构化的文本格式,没有内置的索引或偏移机制,局部修改会破坏整个文件的结构,所以几乎不可能实现——你要么全量加载修改后重写,要么用流式读写的方式“边读边写”,避免把整个文件塞进内存。
问题2:C#中避免全量序列化重写的JSON CRUD方案
针对你提到的小型C#应用,我给你两种实用方案,根据你的场景选择:
方案1:数组末尾快速追加(仅适用于根为数组的场景)
如果你的JSON文件是用户数组(比如[{"id":1}, {"id":2}]),可以直接操作文件流追加元素,不用全量序列化整个列表:
using System.IO; using Newtonsoft.Json; // 假设JsonEntry是你的自定义类 var newUser = new JsonEntry { Id = 3, Name = "Alice" }; var serializedUser = JsonConvert.SerializeObject(newUser); using (var fs = new FileStream("users.json", FileMode.Open, FileAccess.ReadWrite)) { // 定位到文件倒数第二个字符(跳过末尾的"]") fs.Seek(-1, SeekOrigin.End); // 处理可能存在的换行符(如果文件末尾有换行,需要再往前偏移) var buffer = new byte[1]; fs.Read(buffer, 0, 1); if (buffer[0] == '\n' || buffer[0] == '\r') { fs.Seek(-2, SeekOrigin.End); // 再检查一次回车换行 fs.Read(buffer, 0, 1); if (buffer[0] == '\r') { fs.Seek(-1, SeekOrigin.Current); } } // 写入分隔符、新元素和闭合括号 var appendContent = $", {serializedUser}]"; var bytes = System.Text.Encoding.UTF8.GetBytes(appendContent); fs.Write(bytes, 0, bytes.Length); }
⚠️ 注意:这个方法有坑——必须保证文件格式完全正确,且不能有并发写入,否则会直接破坏JSON结构。
方案2:流式读写处理任意CRUD操作(适合大文件或修改/删除场景)
如果需要修改或删除元素,推荐用流式读写:一边读取原文件的JSON令牌,一边写入到临时文件,遇到目标元素时修改,其他内容直接复制。这样不用把整个JSON加载到内存,内存占用极低。
用Newtonsoft.Json实现:
using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Linq; var tempFilePath = Path.GetTempFileName(); using (var reader = new JsonTextReader(new StreamReader("users.json"))) using (var writer = new JsonTextWriter(new StreamWriter(tempFilePath))) { writer.Formatting = Formatting.Indented; // 保持原格式缩进 while (reader.Read()) { // 遇到对象开始令牌时,加载对象并检查是否需要修改 if (reader.TokenType == JsonToken.StartObject) { var userObj = JObject.Load(reader); // 假设要修改id=2的用户名称 if ((int)userObj["id"] == 2) { userObj["name"] = "Bob Updated"; } userObj.WriteTo(writer); } // 其他令牌(比如数组开始、逗号等)直接复制 else { writer.WriteToken(reader); } } } // 替换原文件(可以加个备份步骤更安全) File.Delete("users.json"); File.Move(tempFilePath, "users.json");
用官方System.Text.Json实现(.NET Core 3.0+):
using System.IO; using System.Text.Json; var tempFilePath = Path.GetTempFileName(); using var inputStream = File.OpenRead("users.json"); using var outputStream = File.Create(tempFilePath); using var reader = new Utf8JsonReader(inputStream); using var writer = new Utf8JsonWriter(outputStream, new JsonWriterOptions { Indented = true }); while (reader.Read()) { if (reader.TokenType == JsonTokenType.StartObject) { // 从当前位置读取整个对象 using var objectReader = reader.ReadFromCurrentPosition(); var userElement = JsonElement.ParseValue(ref objectReader); // 检查是否是要修改的用户 if (userElement.TryGetProperty("id", out var idElement) && idElement.GetInt32() == 2) { writer.WriteStartObject(); // 遍历对象属性,修改目标属性 foreach (var property in userElement.EnumerateObject()) { if (property.Name == "name") { writer.WriteString("name", "Bob Updated with System.Text.Json"); } else { property.WriteTo(writer); } } writer.WriteEndObject(); } else { // 直接写入原对象 userElement.WriteTo(writer); } } else { // 复制其他令牌 writer.WriteToken(ref reader); } } // 替换原文件 File.Delete("users.json"); File.Move(tempFilePath, "users.json");
额外建议
如果你的应用需要频繁做CRUD操作,JSON文件其实不是最优选择——它没有内置的事务、索引和并发支持,容易出问题。如果可以的话,换成轻量数据库比如SQLite(支持JSON列),或者嵌入式数据库会更省心。但如果必须用JSON文件,上面的流式方案可以完美解决你的全量操作困扰。
内容的提问来源于stack exchange,提问作者Morgoth
相关产品推荐
相关产品推荐

