使用 Json.net 将大量数据流式传输为 JSON 格式
Posted
技术标签:
【中文标题】使用 Json.net 将大量数据流式传输为 JSON 格式【英文标题】:Streaming large list of data as JSON format using Json.net 【发布时间】:2014-12-03 20:29:02 【问题描述】:使用 MVC 模型,我想编写一个 JsonResult,它将 Json 字符串流式传输到客户端,而不是一次将所有数据转换为 Json 字符串,然后将其流式传输回客户端。 我有一些动作需要在 Json 传输时发送非常大(超过 300,000 条记录),我认为基本的 JsonResult 实现是不可扩展的。
我正在使用 Json.net,我想知道是否有一种方法可以在转换 Json 字符串时对其进行流式传输。
//Current implementation:
response.Write(Newtonsoft.Json.JsonConvert.SerializeObject(Data, formatting));
response.End();
//I know I can use the JsonSerializer instead
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Serialize(textWriter, Data);
但是我不确定如何将块写入 textWriter 并写入响应并调用 reponse.Flush() 直到所有 300,000 条记录都转换为 Json。
这可能吗?
【问题讨论】:
【参考方案1】:假设您的最终输出是一个 JSON 数组,并且每个“块”是该数组中的一项,您可以尝试类似下面的 JsonStreamingResult
类。它使用 JsonTextWriter
将 JSON 写入输出流,并使用 JObject
作为在将每个项目写入写入器之前单独序列化每个项目的方法。您可以传递 JsonStreamingResult
和 IEnumerable
实现,它可以从您的数据源中单独读取项目,这样您就不会一次将它们全部存储在内存中。我没有对此进行广泛的测试,但它应该能让你朝着正确的方向前进。
public class JsonStreamingResult : ActionResult
private IEnumerable itemsToSerialize;
public JsonStreamingResult(IEnumerable itemsToSerialize)
this.itemsToSerialize = itemsToSerialize;
public override void ExecuteResult(ControllerContext context)
var response = context.HttpContext.Response;
response.ContentType = "application/json";
response.ContentEncoding = Encoding.UTF8;
JsonSerializer serializer = new JsonSerializer();
using (StreamWriter sw = new StreamWriter(response.OutputStream))
using (JsonTextWriter writer = new JsonTextWriter(sw))
writer.WriteStartArray();
foreach (object item in itemsToSerialize)
JObject obj = JObject.FromObject(item, serializer);
obj.WriteTo(writer);
writer.Flush();
writer.WriteEndArray();
【讨论】:
该解决方案有效,可以防止内存不足异常,这太棒了。但我认为,如果将成批的记录一起刷新而不是一个一个地刷新,它会更加优化。不确定最佳数字是多少! 是的,我也想知道。您可以轻松地向 JsonStreamingResult 添加一个计数器,使其等待刷新,直到从可枚举中读取了一些记录。如果数字因情况而异,您可以将其设置为参数,以便您可以针对每种不同的用途对其进行调整。此外,在 IEnumerable 方面,您还可以实现一种机制来批量查询数据源,以提高那里的效率。不过,您必须进行大量测量和测试才能看到最有效的方法。 另一个想法虽然可能不可能是测量缓冲区大小并以每 64KB 或类似的方式刷新。不确定我们是否可以检查 JsonTextWriter 中的数据大小 如果你想做类似的事情,你可以尝试用BufferedStream
包裹OutputStream
。但是,this Q & A 似乎表明 .NET 中的大多数流在缓冲方面已经得到了很好的优化。如果是这种情况,也许最好不要调用Flush
,而让流在其内部缓冲区已满时执行其操作。不过不确定;你必须测试它。
一些基准测试表明最有效的方法是使用 serializer.Serialize(writer, data);并一次将所有数据传递给它,因为上面的注释表明 Stream 本身在处理缓冲区方面做得很好,你的代码不需要做一个巨大的循环:)【参考方案2】:
将其留给 .NET 并等待缓冲区已满的问题还有其他问题。
例如: 如果你这样做,一些 json 的内容将被切断,从而导致前端的解析问题。
到目前为止,最好的方法是在您使用批次的情况下在每次迭代时刷新批次,或者如果您的设计是为了这样做,则按单个项目刷新它。
目前我使用 SSE 将数据推送到浏览器并使用分隔符消息“在消息结束时”向浏览器指示连接可以关闭,我知道 SSE 用例用于连续流,但我们也可以使用它帮助分块和批处理响应。
【讨论】:
以上是关于使用 Json.net 将大量数据流式传输为 JSON 格式的主要内容,如果未能解决你的问题,请参考以下文章