我有一个OData响应,如JSON (几MB ),并且要求是流式传输“ JSON的某些部分”,甚至不将其加载到内存中。
例如 :当我在下面的JSON(将以MB为单位)中读取属性“ value[0].Body.Content
”时,我想流式传输此值部分,而无需将其反序列化为字符串类型的Object。因此,基本上将值部分读取到固定大小的字节数组中,然后将该字节数组写入目标流中(重复此步骤,直到完成数据处理为止)。
JSON:
{
"@odata.context": "https://localhost:5555/api/v2.0/$metadata#Me/Messages",
"value": [
{
"@odata.id": "https://localhost:5555/api/v2.0/",
"@odata.etag": "W/\"Something\"",
"Id": "vccvJHDSFds43hwy98fh",
"CreatedDateTime": "2018-12-01T01:47:53Z",
"LastModifiedDateTime": "2018-12-01T01:47:53Z",
"ChangeKey": "SDgf43tsdf",
"WebLink": "https://localhost:5555/?ItemID=dfsgsdfg9876ijhrf",
"Body": {
"ContentType": "HTML",
"Content": "<html>\r\n<body>Huge Data Here\r\n</body>\r\n</html>\r\n"
},
"ToRecipients": [{
"EmailAddress": {
"Name": "ME",
"Address": "me@me.com"
}
}
],
"CcRecipients": [],
"BccRecipients": [],
"ReplyTo": [],
"Flag": {
"FlagStatus": "NotFlagged"
}
}
],
"@odata.nextLink": "http://localhost:5555/rest/jersey/sleep?%24filter=LastDeliveredDateTime+ge+2018-12-01+and+LastDeliveredDateTime+lt+2018-12-02&%24top=50&%24skip=50"
}
尝试的方法:
1. Newtonsoft
我最初尝试使用Newtonsoft流,但它internally converts the data into string and loads into memory。 (这会导致LOH激增,并且直到进行压缩时才会释放内存-我们对工作进程有内存限制,因此无法将其保留在内存中)
**code:**
using (var jsonTextReader = new JsonTextReader(sr))
{
var pool = new CustomArrayPool();
// Checking if pooling will help with memory
jsonTextReader.ArrayPool = pool;
while (jsonTextReader.Read())
{
if (jsonTextReader.TokenType == JsonToken.PropertyName
&& ((string)jsonTextReader.Value).Equals("value"))
{
jsonTextReader.Read();
if (jsonTextReader.TokenType == JsonToken.StartArray)
{
while (jsonTextReader.Read())
{
if (jsonTextReader.TokenType == JsonToken.StartObject)
{
var Current = JToken.Load(jsonTextReader);
// By Now, the LOH Shoots up.
// Avoid below code of converting this JToken back to byte array.
destinationStream.write(Encoding.ASCII.GetBytes(Current.ToString()));
}
else if (jsonTextReader.TokenType == JsonToken.EndArray)
{
break;
}
}
}
}
if (jsonTextReader.TokenType == JsonToken.StartObject)
{
var Current = JToken.Load(jsonTextReader);
// Do some processing with Current
destinationStream.write(Encoding.ASCII.GetBytes(Current.ToString()));
}
}
}
OData.Net:
我在考虑使用OData.Net库looks like it supports streaming of string fields是否可行。但这不能走太远,因为我最终为数据创建了一个模型,这意味着该值将转换为MB的一个字符串对象。
代码
ODataMessageReaderSettings settings = new ODataMessageReaderSettings();
IODataResponseMessage responseMessage = new InMemoryMessage { Stream = stream };
responseMessage.SetHeader("Content-Type", "application/json;odata.metadata=minimal;");
// ODataMessageReader reader = new ODataMessageReader((IODataResponseMessage)message, settings, GetEdmModel());
ODataMessageReader reader = new ODataMessageReader(responseMessage, settings, new EdmModel());
var oDataResourceReader = reader.CreateODataResourceReader();
var property = reader.ReadProperty();
有什么想法如何使用OData.Net/Newtonsoft和某些字段的流值来部分解析此JSON?
这样做的唯一方法是手动解析流吗?
答案 0 :(得分:3)
如果要将JSON的一部分从一个流复制到另一个流,则可以使用JsonWriter.WriteToken(JsonReader)
来更高效地执行此操作,从而避免了中间的Current = JToken.Load(jsonTextReader)
和Encoding.ASCII.GetBytes(Current.ToString())
表示形式及其相关的内存开销:
using (var textWriter = new StreamWriter(destinationStream, new UTF8Encoding(false, true), 1024, true))
using (var jsonWriter = new JsonTextWriter(textWriter) { Formatting = Formatting.Indented, CloseOutput = false })
{
// Use Formatting.Indented or Formatting.None as required.
jsonWriter.WriteToken(jsonTextReader);
}
但是,Json.NET的JsonTextReader
无法以与XmlReader.ReadValueChunk()
相同的方式读取“块”中的单个字符串值。它将始终完全实现每个原子串值。如果您的字符串值太大而无法在大对象堆上使用,那么即使使用JsonWriter.WriteToken()
也不会阻止这些字符串完全加载到内存中。
作为替代方案,您可以考虑由JsonReaderWriterFactory
返回的读者和作家。 DataContractJsonSerializer
使用这些读取器和写入器,将read和written上的JSON即时转换为XML。由于这些读取器和写入器的基类为XmlReader
和XmlWriter
,因此它们 do 支持以块形式读取和写入字符串值。适当地使用它们将避免在大型对象堆中分配字符串。
为此,首先定义以下扩展方法,这些方法将JSON值的选定子集从输入流复制到输出流,如要流数据的路径所指定:
public static class JsonExtensions
{
public static void StreamNested(Stream from, Stream to, string [] path)
{
var reversed = path.Reverse().ToArray();
using (var xr = JsonReaderWriterFactory.CreateJsonReader(from, XmlDictionaryReaderQuotas.Max))
{
foreach (var subReader in xr.ReadSubtrees(s => s.Select(n => n.LocalName).SequenceEqual(reversed)))
{
using (var xw = JsonReaderWriterFactory.CreateJsonWriter(to, Encoding.UTF8, false))
{
subReader.MoveToContent();
xw.WriteStartElement("root");
xw.WriteAttributes(subReader, true);
subReader.Read();
while (!subReader.EOF)
{
if (subReader.NodeType == XmlNodeType.Element && subReader.Depth == 1)
xw.WriteNode(subReader, true);
else
subReader.Read();
}
xw.WriteEndElement();
}
}
}
}
}
public static class XmlReaderExtensions
{
public static IEnumerable<XmlReader> ReadSubtrees(this XmlReader xmlReader, Predicate<Stack<XName>> filter)
{
Stack<XName> names = new Stack<XName>();
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element)
{
names.Push(XName.Get(xmlReader.LocalName, xmlReader.NamespaceURI));
if (filter(names))
{
using (var subReader = xmlReader.ReadSubtree())
{
yield return subReader;
}
}
}
if ((xmlReader.NodeType == XmlNodeType.Element && xmlReader.IsEmptyElement)
|| xmlReader.NodeType == XmlNodeType.EndElement)
{
names.Pop();
}
}
}
}
现在,string [] path
的{{1}}参数不是任何类型的jsonpath路径。相反,它是一个路径,该路径与JsonReaderWriterFactory.CreateJsonReader()
返回的StreamNested()
转换后的XML元素的层次结构相对应,该XML元素对应于您要选择的JSON。用于此的映射反过来,翻译则由Microsoft在 Mapping Between JSON and XML 中记录。要仅选择和流传输与XmlReader
匹配的JSON值,所需的XML路径为value[*]
。因此,您可以通过执行以下操作选择并流式传输所需的嵌套对象:
//root/value/item
注意:
Mapping Between JSON and XML 有点复杂。使用以下扩展方法将一些示例JSON加载到JsonExtensions.StreamNested(inputStream, destinationStream, new[] { "root", "value", "item" });
中通常会更容易:
XDocument
然后观察确定正确的XML路径。
有关相关问题,请参见 JObject.SelectToken Equivalent in .NET 。