使用流通过Json.NET创建BSON字节数组(用于文件格式)

4
我们需要BSON的等效内容。
{
    "Header": {
        "SubHeader1": {
            "Name": "Bond",
            "License": 7
        },
        "SubHeader2": {
            "IsActive": true
        }
    },
    "Payload": /* This will be a 40GB byte stream! */
}

但我们得到的是:
如你所见,有效载荷在头部之前!
我们正在使用Json.NET的BSON编写器(Bson.BsonWriter.WriteValue(byte[] value)),但它只接受实际的byte[],而不是Stream。由于我们的有效载荷将达到数十GB,我们必须使用流,因此我们已经尝试解决这个问题(下面的代码),但这给我们带来了上面显示的不正确结果。
public void Expt()
{
    // Just some structure classes, defined below
    var fileStruct = new FileStructure();

    using (Stream outputSt = new FileStream("TestBinary.bson", FileMode.Create))
    {
        var serializer = new JsonSerializer();
        var bw = new BsonWriter(outputSt);

        // Start
        bw.WriteStartObject();

        // Write header            
        bw.WritePropertyName("Header");
        serializer.Serialize(bw, fileStruct.Header);

        // Write payload
        bw.WritePropertyName("Payload");
        bw.Flush(); // <== flush !                
        // In reality we 40GB into the stream, dummy example for now
        byte[] dummyPayload = Encoding.UTF8.GetBytes("This will be a 40GB byte stream!");
        outputSt.Write(dummyPayload, 0, dummyPayload.Length);

        // End
        bw.WriteEndObject();
    }    
}

这似乎是没有同步/刷新缓冲区的典型情况,尽管我们在将有效载荷写入底层流之前实际上发出了Json.NET的Flush命令。
问题:还有其他方法吗?我们不想分叉Json.NET的源代码(并探索其内部管道)或以某种方式重新发明轮子...
详细信息:支持结构类是(如果您想要重现此问题):
public class FileStructure
{
    public TopHeader Header { get; set; }
    public byte[] Payload { get; set; }

    public FileStructure()
    {
        Header = new TopHeader
            {
                SubHeader1 = new SubHeader1 {Name = "Bond", License = 007},
                SubHeader2 = new SubHeader2 {IsActive = true}
            };
    }
}

public class TopHeader
{
    public SubHeader1 SubHeader1 { get; set; }
    public SubHeader2 SubHeader2 { get; set; }
}

public class SubHeader1
{
    public string Name { get; set; }
    public int License { get; set; }
}

public class SubHeader2
{
    public bool IsActive { get; set; }
}

BsonWriter 仅在对象的末尾写入数据(请参见 BsonWriter.WriteEnd)。看起来你需要将其复制到你的项目中,并修改相当多的类(BsonWriterBsonBinaryWriter,整个 BsonToken 层次结构等),以实现写入流,因为它们不是设计成可扩展的。这个功能看起来非常有用,所以我建议修改库的代码并发起一个 pull request。顺便说一下,会有一些限制;其中一个要求是流需要支持告诉它的长度。 - Athari
最初你的权宜之计是什么,关于BSON规范要求使用32位有符号整数来描述流长度(这将限制您的最大“负载”大小为2 GB)? - DuckMaestro
1
@DuckMaestro:黑客/扩展规范。我们考虑过将第一个uint32黑客入uint64(4字节= > 8字节),或将uint32长度= 0指定为“忽略前4个字节并读取下一个8个字节/uint64”的特殊含义。幸运的是,我们也没有必须采用这种方式,因为我们采用了另一种替代方案(见下文)。BSON很好,但仍有一些发展空间以实现通用性。 - DeepSpace101
1个回答

1

好的,我们在这里达成了一些妥协,因为我们现在没有时间修复本来很好的Json.NET库。由于我们只有在最后才能获得流,所以我们现在使用BSON作为头部(足够小,可以使用byte[]),然后将其传递给标准流写入器,即表示如下:

{
    "SubHeader1": {
        "Name": "Bond",
        "License": 7
    },
    "SubHeader2": {
        "IsActive": true
    }
} /* End of valid BSON */
// <= Our Stream is written here, raw byte stream, no BSON

如果有一个统一的BSON布局会更美观,但在没有它的情况下,这也很好用。可能会更快一些!如果未来有人发现更好的答案,我们会倾听。


另外,我们最终放弃了BSON并用ProtoBuf风格的头文件进行了替换。使用Marc Gavell的实现,您可以使用类似于Serializer.DeserializeWithLengthPrefix<YourHeader>(readSteam, PrefixStyle.Base128);的东西...只是提供一下信息。 - DeepSpace101

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接