一次反序列化json数组流一项
Posted
技术标签:
【中文标题】一次反序列化json数组流一项【英文标题】:Deserialize json array stream one item at a time 【发布时间】:2013-12-20 20:43:51 【问题描述】:我将一个大对象数组序列化为一个 json http 响应流。现在我想一次一个地从流中反序列化这些对象。是否有任何 c# 库可以让我这样做?我查看了 json.net,但似乎我必须一次反序列化完整的对象数组。
[large json object,large json object.....]
澄清:我想一次从流中读取一个json对象并反序列化它。
【问题讨论】:
你究竟为什么想要这样的行为? 因为我不想将整个大对象数组保留在内存中。 您可能需要使用 Json.NET 中的JsonTextReader
并手动读取令牌。
【参考方案1】:
为了逐步读取 JSON,您需要将 JsonTextReader
与 StreamReader
结合使用。但是,您不一定必须从阅读器手动读取所有 JSON。您应该能够利用 Linq-To-JSON API 从阅读器中加载每个大对象,以便更轻松地使用它。
举个简单的例子,假设我有一个如下所示的 JSON 文件:
[
"name": "foo",
"id": 1
,
"name": "bar",
"id": 2
,
"name": "baz",
"id": 3
]
从文件中以增量方式读取它的代码可能如下所示。 (在您的情况下,您可以用您的响应流替换 FileStream。)
using (FileStream fs = new FileStream(@"C:\temp\data.json", FileMode.Open, FileAccess.Read))
using (StreamReader sr = new StreamReader(fs))
using (JsonTextReader reader = new JsonTextReader(sr))
while (reader.Read())
if (reader.TokenType == JsonToken.StartObject)
// Load each object from the stream and do something with it
JObject obj = JObject.Load(reader);
Console.WriteLine(obj["id"] + " - " + obj["name"]);
上面的输出如下所示:
1 - foo
2 - bar
3 - baz
【讨论】:
这与我想出的解决方案基本相同。除了我做一个新的 JsonSerializer().DeserializeJsonSerializer.Create().Deserialize<T>(reader, desiredType);
,因为 JObject.Load
从来没有工作过(也没有抛出错误——这是最奇怪的)。
请记住,与 serializer.Deserialize 相比,使用 JObject.Load 的性能不高。所以总是使用反序列化【参考方案2】:
我已经简化了我的解析器/反序列化器的样本/测试之一,以更直接地回答这个问题的用例。
这是测试数据:
https://github.com/ysharplanguage/FastJsonParser/tree/master/JsonTest/TestData
(参见fathers.json.txt)
下面是示例代码:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
// Our stuff
using System.Text.Json;
//...
public class FathersData
public Father[] fathers get; set;
public class Someone
public string name get; set;
public class Father : Someone
public int id get; set;
public bool married get; set;
// Lists...
public List<Son> sons get; set;
// ... or arrays for collections, that's fine:
public Daughter[] daughters get; set;
public class Child : Someone
public int age get; set;
public class Son : Child
public class Daughter : Child
public string maidenName get; set;
//...
static void FilteredFatherStreamTestSimplified()
// Get our parser:
var parser = new JsonParser();
// (Note this will be invoked thanks to the "filters" dictionary below)
Func<object, object> filteredFatherStreamCallback = obj =>
Father father = (obj as Father);
// Output only the individual fathers that the filters decided to keep (i.e., when obj.Type equals typeof(Father)),
// but don't output (even once) the resulting array (i.e., when obj.Type equals typeof(Father[])):
if (father != null)
Console.WriteLine("\t\tId : 0\t\tName : 1", father.id, father.name);
// Do not project the filtered data in any specific way otherwise,
// just return it deserialized as-is:
return obj;
;
// Prepare our filter, and thus:
// 1) we want only the last five (5) fathers (array index in the resulting "Father[]" >= 29,995),
// (assuming we somehow have prior knowledge that the total count is 30,000)
// and for each of them,
// 2) we're interested in deserializing them with only their "id" and "name" properties
var filters =
new Dictionary<Type, Func<Type, object, object, int, Func<object, object>>>
// We don't care about anything but these 2 properties:
typeof(Father), // Note the type
(type, obj, key, index) =>
((key as string) == "id" || (key as string) == "name") ?
filteredFatherStreamCallback :
JsonParser.Skip
,
// We want to pick only the last 5 fathers from the source:
typeof(Father[]), // Note the type
(type, obj, key, index) =>
(index >= 29995) ?
filteredFatherStreamCallback :
JsonParser.Skip
;
// Read, parse, and deserialize fathers.json.txt in a streamed fashion,
// and using the above filters, along with the callback we've set up:
using (var reader = new System.IO.StreamReader(FATHERS_TEST_FILE_PATH))
FathersData data = parser.Parse<FathersData>(reader, filters);
System.Diagnostics.Debug.Assert
(
(data != null) &&
(data.fathers != null) &&
(data.fathers.Length == 5)
);
foreach (var i in Enumerable.Range(29995, 5))
System.Diagnostics.Debug.Assert
(
(data.fathers[i - 29995].id == i) &&
!String.IsNullOrEmpty(data.fathers[i - 29995].name)
);
Console.ReadKey();
其余位可在此处获得:
https://github.com/ysharplanguage/FastJsonParser
'HTH,
【讨论】:
【参考方案3】:这是我的解决方案(结合不同来源,但主要基于Brian Rogers 解决方案)将巨大的 JSON 文件(对象数组)转换为任何通用对象的 XML 文件。
JSON 看起来像这样:
"Order": [
order object 1,
order object 2,
...
order object 10000,
]
输出 XML:
<Order>...</Order>
<Order>...</Order>
<Order>...</Order>
C#代码:
XmlWriterSettings xws = new XmlWriterSettings OmitXmlDeclaration = true ;
using (StreamWriter sw = new StreamWriter(xmlFile))
using (FileStream fs = new FileStream(jsonFile, FileMode.Open, FileAccess.Read))
using (StreamReader sr = new StreamReader(fs))
using (JsonTextReader reader = new JsonTextReader(sr))
//sw.Write("<root>");
while (reader.Read())
if (reader.TokenType == JsonToken.StartArray)
while (reader.Read())
if (reader.TokenType == JsonToken.StartObject)
JObject obj = JObject.Load(reader);
XmlDocument doc = JsonConvert.DeserializeXmlNode(obj.ToString(), "Order");
sw.Write(doc.InnerXml); // a line of XML code <Order>...</Order>
sw.Write("\n");
//this approach produces not strictly valid XML document
//add root element at the beginning and at the end to make it valid XML
//sw.Write("</root>");
【讨论】:
【参考方案4】:使用Cinchoo ETL - 一个开源库,您可以高效地解析大型 JSON,并且内存占用少。由于对象是在基于流的拉模型中构造和返回的
using (var p = new ChoJSONReader(** YOUR JSON FILE **))
foreach (var rec in p)
Console.WriteLine($"Name: rec.name, Id: rec.id");
如需了解更多信息,请访问 codeproject 文章。
希望对你有帮助。
【讨论】:
以上是关于一次反序列化json数组流一项的主要内容,如果未能解决你的问题,请参考以下文章