From 76f51b49c9d4eee3c5dec3d0917f50cb26f12ef5 Mon Sep 17 00:00:00 2001 From: Jonas Kamsker <11245306+JKamsker@users.noreply.github.com> Date: Thu, 2 Oct 2025 00:20:46 +0200 Subject: [PATCH] Add pooled buffer serialization overloads --- LiteDB.Tests/Document/Bson_Tests.cs | 37 ++++++++++++++ LiteDB/Document/Bson/BsonSerializer.cs | 68 +++++++++++++++++++++++--- LiteDB/LiteDB.csproj | 1 + README.md | 11 +++++ 4 files changed, 110 insertions(+), 7 deletions(-) diff --git a/LiteDB.Tests/Document/Bson_Tests.cs b/LiteDB.Tests/Document/Bson_Tests.cs index c8b464caa..f90909b08 100644 --- a/LiteDB.Tests/Document/Bson_Tests.cs +++ b/LiteDB.Tests/Document/Bson_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using FluentAssertions; using Xunit; @@ -138,5 +139,41 @@ static T DeserializeAnonymous(BsonMapper mapper, BsonDocument doc, T obj) return mapper.Deserialize(doc); } } + + [Fact] + public void Serialize_To_ArrayBufferWriter_Matches_Byte_Array() + { + var document = CreateDoc(); + var expected = BsonSerializer.Serialize(document); + + var bufferWriter = new ArrayBufferWriter(); + var written = BsonSerializer.Serialize(document, bufferWriter); + + written.Should().Be(expected.Length); + bufferWriter.WrittenCount.Should().Be(expected.Length); + bufferWriter.WrittenSpan.ToArray().Should().Equal(expected); + } + + [Fact] + public void Serialize_To_Pooled_Buffers_Preserves_Output() + { + var document = CreateDoc(); + var expected = BsonSerializer.Serialize(document); + + var rented = ArrayPool.Shared.Rent(expected.Length + 8); + + try + { + var memory = new Memory(rented, 2, expected.Length); + var memoryWritten = BsonSerializer.Serialize(document, memory); + + memoryWritten.Should().Be(expected.Length); + memory.Span.Slice(0, memoryWritten).ToArray().Should().Equal(expected); + } + finally + { + ArrayPool.Shared.Return(rented); + } + } } } \ No newline at end of file diff --git a/LiteDB/Document/Bson/BsonSerializer.cs b/LiteDB/Document/Bson/BsonSerializer.cs index 9942086a5..6665b29ff 100644 --- a/LiteDB/Document/Bson/BsonSerializer.cs +++ b/LiteDB/Document/Bson/BsonSerializer.cs @@ -1,7 +1,8 @@ -using LiteDB.Engine; +using LiteDB.Engine; using System; +using System.Buffers; using System.Collections.Generic; -using System.IO; +using System.Runtime.InteropServices; using static LiteDB.Constants; namespace LiteDB @@ -13,21 +14,74 @@ namespace LiteDB public class BsonSerializer { /// - /// Serialize BsonDocument into a binary array + /// Serialize into a binary array. /// public static byte[] Serialize(BsonDocument doc) { if (doc == null) throw new ArgumentNullException(nameof(doc)); - var buffer = new byte[doc.GetBytesCount(true)]; + var bytesRequired = doc.GetBytesCount(true); + var buffer = new byte[bytesRequired]; - using (var writer = new BufferWriter(buffer)) + Serialize(doc, buffer.AsMemory()); + + return buffer; + } + + /// + /// Serialize into the provided . + /// Returns the number of bytes written to . + /// + /// Thrown when is null. + /// Thrown when is shorter than the serialized document. + /// Thrown when is not backed by a managed array. + public static int Serialize(BsonDocument doc, Memory destination) + { + if (doc == null) throw new ArgumentNullException(nameof(doc)); + + var bytesRequired = doc.GetBytesCount(true); + + if (destination.Length < bytesRequired) + { + throw new ArgumentException($"Destination memory must be at least {bytesRequired} bytes long", nameof(destination)); + } + + if (!MemoryMarshal.TryGetArray(destination, out ArraySegment segment)) + { + throw new NotSupportedException("The destination memory must be backed by a managed array."); + } + + using (var writer = new BufferWriter(new BufferSlice(segment.Array, segment.Offset, bytesRequired))) { writer.WriteDocument(doc, false); } - return buffer; + return bytesRequired; + } + +#if !NETSTANDARD2_0 + /// + /// Serialize into the supplied . + /// Returns the number of bytes written to . + /// + /// Thrown when or is null. + /// Thrown when the provided exposes a buffer that is shorter than the serialized document. + /// Thrown when the buffer provided by is not backed by a managed array. + public static int Serialize(BsonDocument doc, IBufferWriter writer) + { + if (doc == null) throw new ArgumentNullException(nameof(doc)); + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + var bytesRequired = doc.GetBytesCount(true); + + var memory = writer.GetMemory(bytesRequired); + var written = Serialize(doc, memory); + + writer.Advance(written); + + return written; } +#endif /// /// Deserialize binary data into BsonDocument @@ -42,4 +96,4 @@ public static BsonDocument Deserialize(byte[] buffer, bool utcDate = false, Hash } } } -} \ No newline at end of file +} diff --git a/LiteDB/LiteDB.csproj b/LiteDB/LiteDB.csproj index 7ebb6d4ce..f66f875c8 100644 --- a/LiteDB/LiteDB.csproj +++ b/LiteDB/LiteDB.csproj @@ -52,6 +52,7 @@ + diff --git a/README.md b/README.md index 2859e3b1d..da22bdc44 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,17 @@ using(var db = new LiteDatabase(@"MyData.db")) } ``` +### BSON serialization + +If you need direct access to raw BSON, `BsonSerializer` exposes overloads that write into caller-supplied `Memory` or `IBufferWriter` instances. This allows applications to reuse buffers from pools without forcing an intermediate array allocation. + +```C# +var writer = new ArrayBufferWriter(); +var written = BsonSerializer.Serialize(document, writer); + +// written now contains the BSON length and writer.WrittenSpan holds the bytes +``` + Using fluent mapper and cross document reference for more complex data models ```C#