我不喜欢在流接口之间使用MemoryStream对象。它们很尴尬,需要你重新寻找开始,在要求苛刻的情况下也会达到内存使用的峰值。
有时一个实用程序只能以某种方式工作。也许它会输出byte[]s,或者写入流,或者是您从中读取的管道中的流,从而将数据拉过。
这个Newtonsoft JSON序列化程序是一个只能写入流的实用程序。
var js = new Newtonsoft.Json.JsonSerializer();
var sw = new StreamWriter(ps);
js.Serialize(sw, o);
这对我来说是个问题,因为我想连锁:
- IEnumerable
- JSON序列化
- GZIP压缩
- HTTP到客户端
- (网络)
- 来自服务器的HTTP
- GZIP解压缩
- JSON反序列化
- IEnumerable
除了让JSON反序列化器提供一个漂亮的IEnumerable接口的困难之外,其余部分都没有提供适合流水线的接口。即使是GZIP压缩端也是错误的。
理想情况下,在服务器端,我可以做到:
IEnumerable<object> o = GetData();
var js = new Newtonsoft.Json.JsonSerialization(o);
var gz = new System.IO.Compression.GZipStream(js, System.IO.Compression.CompressionMode.Compress, true);
return new FileStreamResult(gz, "application/x-gzip");
我可以扩展Newtonsoft项目以提供管道实现,我也可以这样做。但在那之前,我需要一个解决方案,我相信其他公用事业(包括BCL GZipStream)也需要一个。
- 有没有什么解决方案可以让人们更有效地加入这些公用事业
- 有没有一个库包含用于这种情况的适配器
我在建这样一个图书馆,没想到会有这样一个。
答案是全新的StreamAdaptor项目:https://bitbucket.org/merarischroeder/alivate-stream-adaptor.它仍然需要一些工作——将它打包为NuGet包会很好,但它已经全部存在并经过了测试。
所以界面看起来有点像这样:
var data = GetData(); //Get the source data
var sa = new StreamAdaptor(); //This is what wraps the write-only utility source
sa.UpstreamSource((ps) => //ps is the dummy stream which does most of the magic
{
//This anon. function is run on a separate thread and can therefore be blocked
var sw = new StreamWriter(ps);
sw.AutoFlush = true;
var js = new Newtonsoft.Json.JsonSerializer();
js.Serialize(sw, data); //This is the main component of the implementation
sw.Flush();
});
var sa2 = new StreamAdaptor();
sa2.UpstreamSource((ps) =>
{
using (var gz = new System.IO.Compression.GZipStream(ps, System.IO.Compression.CompressionMode.Compress, true))
sa.CopyTo(gz);
});
有了对直通管道的自然支持,反向过程更容易
System.IO.Compression.GZipStream sw = new System.IO.Compression.GZipStream(sa2, System.IO.Compression.CompressionMode.Decompress);
var jsonTextReader = new JsonTextReader(new StreamReader(sw));
return TestA.Deserialize(jsonTextReader);
我还演示了IEnumerable<>的变通方法反序列化问题。它要求您使用JsonTextReader创建自己的反序列化程序,但效果很好。
序列化程序本机支持IEnumerable。上面的GetData函数使用IEnumerable函数(以及其他函数)为序列化程序设置数据源:
public static IEnumerable<TestB> GetTestBs()
{
for (int i = 0; i < 2; i++)
{
var b = new TestB();
b.A = "A";
b.B = "B";
b.C = TestB.GetCs();
yield return b;
}
}
这是沙漠化,需要一个变通办法。请记住,IEnumerable<>属性需要全部列在JSON流/对象的末尾,因为枚举是延迟的,但JSON反序列化是线性的。
反序列化入口点:
public static TestA Deserialize(JsonTextReader reader)
{
TestA a = new TestA();
reader.Read();
reader.Read();
if (!reader.Value.Equals("TestBs"))
throw new Exception("Expected property 'TestBs' first");
reader.Read(); //Start array
a.TestBs = DeserializeTestBs(reader); //IEnumerable property last
return a;
}
IEnumerable反序列化程序函数之一:
static IEnumerable<TestB> DeserializeTestBs(JsonTextReader reader)
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray)
break;
yield return TestB.Deserialize(reader);
}
reader.Read(); //End of object
}
当然,这可以通过反复试验来实现,尽管需要在JSON.NET中提供内置支持。