diff --git a/.gitignore b/.gitignore
index bc78471..87e72e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+# This is only for local build
+PackageRedirects.props
+
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..2eb53eb
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,24 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": ".NET Core Launch (console)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ "program": "${workspaceFolder}/artifacts/bin/bench/debug/bench.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/bench",
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..49dab52
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/serde.msgpack.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/serde.msgpack.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/serde.msgpack.sln"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000..e8704e1
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,18 @@
+
+
+
+
+ <_ExcludePackage Include="@(PackageRedirect)" Exclude="@(PackageReference)">
+ PackageName
+ %(PackageName)
+
+ <_ExcludeProjects Include ="@(_ExcludePackage->Metadata('ProjectPath'))" />
+
+ %(ReferenceOutputAssembly)
+ %(OutputItemType)
+
+
+
+
+
\ No newline at end of file
diff --git a/bench/DataGenerator.cs b/bench/DataGenerator.cs
index 5338905..691c509 100644
--- a/bench/DataGenerator.cs
+++ b/bench/DataGenerator.cs
@@ -24,19 +24,6 @@ public static T GenerateSerialize() where T : Serde.ISerializeProvider
}
- public static Location CreateLocation() => new Location
- {
- Id = 1234,
- Address1 = "The Street Name",
- Address2 = "20/11",
- City = "The City",
- State = "The State",
- PostalCode = "abc-12",
- Name = "Nonexisting",
- PhoneNumber = "+0 11 222 333 44",
- Country = "The Greatest"
- };
-
public static byte[] GenerateDeserialize()
{
if (typeof(T) == typeof(LoginViewModel))
diff --git a/bench/DeserializeFromString.cs b/bench/DeserializeFromString.cs
index bd7ce38..7610140 100644
--- a/bench/DeserializeFromString.cs
+++ b/bench/DeserializeFromString.cs
@@ -8,15 +8,13 @@
namespace Benchmarks
{
- [GenericTypeArguments(typeof(Location), typeof(LocationWrap))]
- public class DeserializeFromString
+ [GenericTypeArguments(typeof(Location))]
+ public class DeserializeFromString
where T : Serde.IDeserializeProvider
- where U : Serde.IDeserializeProvider
{
private byte[] value = null!;
- private readonly IDeserialize _proxy = T.DeserializeInstance;
- private readonly IDeserialize _manualProxy = U.DeserializeInstance;
+ private readonly IDeserialize _proxy = T.Instance;
[GlobalSetup]
public void Setup()
@@ -32,11 +30,5 @@ public void Setup()
[Benchmark]
public T SerdeMsgPack() => Serde.MsgPack.MsgPackSerializer.Deserialize>(value, _proxy);
-
- [Benchmark]
- public T SerdeMsgPackManual() => Serde.MsgPack.MsgPackSerializer.Deserialize>(value, _manualProxy);
-
- // DataContractJsonSerializer does not provide an API to serialize to string
- // so it's not included here (apples vs apples thing)
}
}
\ No newline at end of file
diff --git a/bench/Program.cs b/bench/Program.cs
index 705b599..d627cd0 100644
--- a/bench/Program.cs
+++ b/bench/Program.cs
@@ -15,7 +15,7 @@
}
var loc1 = MessagePackSerializer.Deserialize(msg1);
-var loc2 = Serde.MsgPack.MsgPackSerializer.Deserialize(msg1, LocationWrap.Instance);
+var loc2 = Serde.MsgPack.MsgPackSerializer.Deserialize(msg1);
Console.WriteLine("Checking correctness of serialization: " + (loc1 == loc2));
if (loc1 != loc2)
@@ -31,5 +31,5 @@ Serialization is not correct
}
var config = DefaultConfig.Instance.AddDiagnoser(MemoryDiagnoser.Default);
-var summary = BenchmarkSwitcher.FromAssembly(typeof(DeserializeFromString<,>).Assembly)
+var summary = BenchmarkSwitcher.FromAssembly(typeof(DeserializeFromString<>).Assembly)
.Run(args, config);
\ No newline at end of file
diff --git a/bench/SampleTypes.cs b/bench/SampleTypes.cs
index dd3f887..7248993 100644
--- a/bench/SampleTypes.cs
+++ b/bench/SampleTypes.cs
@@ -17,26 +17,18 @@ public partial class LoginViewModel
}
[GenerateSerialize, GenerateDeserialize]
- [MessagePackObject]
+ [MessagePackObject(keyAsPropertyName: true)]
+ [SerdeTypeOptions(MemberFormat = MemberFormat.None)]
public partial record Location
{
- [Key(0)]
public int Id { get; set; }
- [Key(1)]
public string Address1 { get; set; }
- [Key(2)]
public string Address2 { get; set; }
- [Key(3)]
public string City { get; set; }
- [Key(4)]
public string State { get; set; }
- [Key(5)]
public string PostalCode { get; set; }
- [Key(6)]
public string Name { get; set; }
- [Key(7)]
public string PhoneNumber { get; set; }
- [Key(8)]
public string Country { get; set; }
public static Location Sample => new Location
@@ -52,110 +44,4 @@ public partial record Location
Country = "The Greatest"
};
}
-
- public sealed partial class LocationWrap : IDeserialize, IDeserializeProvider
- {
- public static LocationWrap Instance { get; } = new();
- static IDeserialize IDeserializeProvider.DeserializeInstance => Instance;
- private LocationWrap() { }
-
- public static ISerdeInfo SerdeInfo { get; } = Serde.SerdeInfo.MakeCustom(
- "Location",
- typeof(Location).GetCustomAttributesData(),
- [
- ("id", Int32Proxy.SerdeInfo, typeof(Location).GetProperty("Id")!),
- ("address1", StringProxy.SerdeInfo, typeof(Location).GetProperty("Address1")!),
- ("address2", StringProxy.SerdeInfo, typeof(Location).GetProperty("Address2")!),
- ("city", StringProxy.SerdeInfo, typeof(Location).GetProperty("City")!),
- ("state", StringProxy.SerdeInfo, typeof(Location).GetProperty("State")!),
- ("postalCode", StringProxy.SerdeInfo, typeof(Location).GetProperty("PostalCode")!),
- ("name", StringProxy.SerdeInfo, typeof(Location).GetProperty("Name")!),
- ("phoneNumber", StringProxy.SerdeInfo, typeof(Location).GetProperty("PhoneNumber")!),
- ("country", StringProxy.SerdeInfo, typeof(Location).GetProperty("Country")!)
- ]);
-
- Benchmarks.Location Serde.IDeserialize.Deserialize(IDeserializer deserializer)
- {
- int _l_id = default !;
- string _l_address1 = default !;
- string _l_address2 = default !;
- string _l_city = default !;
- string _l_state = default !;
- string _l_postalcode = default !;
- string _l_name = default !;
- string _l_phonenumber = default !;
- string _l_country = default !;
- ushort _r_assignedValid = 0b0;
-
- var _l_serdeInfo = SerdeInfo;
- var typeDeserialize = deserializer.ReadType(_l_serdeInfo);
- int index;
- while ((index = typeDeserialize.TryReadIndex(_l_serdeInfo, out _)) != IDeserializeType.EndOfType)
- {
- switch (index)
- {
- case 0:
- _l_id = typeDeserialize.ReadI32(index);
- _r_assignedValid |= ((ushort)1) << 0;
- break;
- case 1:
- _l_address1 = typeDeserialize.ReadString(index);
- _r_assignedValid |= ((ushort)1) << 1;
- break;
- case 2:
- _l_address2 = typeDeserialize.ReadString(index);
- _r_assignedValid |= ((ushort)1) << 2;
- break;
- case 3:
- _l_city = typeDeserialize.ReadString(index);
- _r_assignedValid |= ((ushort)1) << 3;
- break;
- case 4:
- _l_state = typeDeserialize.ReadString(index);
- _r_assignedValid |= ((ushort)1) << 4;
- break;
- case 5:
- _l_postalcode = typeDeserialize.ReadString(index);
- _r_assignedValid |= ((ushort)1) << 5;
- break;
- case 6:
- _l_name = typeDeserialize.ReadString(index);
- _r_assignedValid |= ((ushort)1) << 6;
- break;
- case 7:
- _l_phonenumber = typeDeserialize.ReadString(index);
- _r_assignedValid |= ((ushort)1) << 7;
- break;
- case 8:
- _l_country = typeDeserialize.ReadString(index);
- _r_assignedValid |= ((ushort)1) << 8;
- break;
- case Serde.IDeserializeType.IndexNotFound:
- typeDeserialize.SkipValue();
- break;
- default:
- throw new InvalidOperationException("Unexpected index: " + index);
- }
- }
-
- if (_r_assignedValid != 0b111111111)
- {
- throw Serde.DeserializeException.UnassignedMember();
- }
-
- var newType = new Benchmarks.Location()
- {
- Id = _l_id,
- Address1 = _l_address1,
- Address2 = _l_address2,
- City = _l_city,
- State = _l_state,
- PostalCode = _l_postalcode,
- Name = _l_name,
- PhoneNumber = _l_phonenumber,
- Country = _l_country,
- };
- return newType;
- }
- }
}
\ No newline at end of file
diff --git a/bench/SerializeToBytes.cs b/bench/SerializeToBytes.cs
new file mode 100644
index 0000000..046b405
--- /dev/null
+++ b/bench/SerializeToBytes.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using BenchmarkDotNet.Attributes;
+using MessagePack;
+using Serde;
+
+namespace Benchmarks
+{
+ [GenericTypeArguments(typeof(Location))]
+ public class SerializeToBytes
+ where T : Serde.ISerializeProvider
+ {
+ private T value = default!;
+
+ private readonly ISerialize _proxy = T.Instance;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ value = DataGenerator.GenerateSerialize();
+ }
+
+ [Benchmark]
+ public byte[] MessagePack()
+ {
+ return MessagePackSerializer.Serialize(value);
+ }
+
+ [Benchmark]
+ public byte[] SerdeMsgPack() => Serde.MsgPack.MsgPackSerializer.Serialize(value, _proxy);
+ }
+}
\ No newline at end of file
diff --git a/src/MsgPackSerializer.cs b/src/MsgPackSerializer.cs
index ddd944a..903e09b 100644
--- a/src/MsgPackSerializer.cs
+++ b/src/MsgPackSerializer.cs
@@ -7,18 +7,11 @@ public static class MsgPackSerializer
{
public static byte[] Serialize(T value)
where T : ISerializeProvider
- {
- using var buffer = new ScratchBuffer();
- var writer = new MsgPackWriter(buffer);
- var serializeObject = T.SerializeInstance;
- serializeObject.Serialize(value, writer);
- return buffer.Span.ToArray();
- }
+ => Serialize(value, T.Instance);
- public static byte[] Serialize(T value, U proxy)
- where U : ISerialize
+ public static byte[] Serialize(T value, ISerialize proxy)
{
- using var buffer = new ScratchBuffer();
+ using var buffer = new ScratchBuffer(1024);
var writer = new MsgPackWriter(buffer);
proxy.Serialize(value, writer);
return buffer.Span.ToArray();
@@ -31,4 +24,11 @@ public static T Deserialize(byte[] bytes, U proxy)
using var reader = new MsgPackReader(byteBuffer);
return proxy.Deserialize(reader);
}
+ public static T Deserialize(byte[] bytes)
+ where T : IDeserializeProvider
+ {
+ var byteBuffer = new ArrayBufReader(bytes);
+ using var reader = new MsgPackReader(byteBuffer);
+ return T.Instance.Deserialize(reader);
+ }
}
diff --git a/src/ScratchBuffer.cs b/src/ScratchBuffer.cs
index 7a48772..1a9731f 100644
--- a/src/ScratchBuffer.cs
+++ b/src/ScratchBuffer.cs
@@ -6,6 +6,16 @@
internal sealed class ScratchBuffer : IDisposable
{
+ public ScratchBuffer() { }
+ public ScratchBuffer(int capacity)
+ {
+ if (capacity < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity));
+ }
+ _rented = ArrayPool.Shared.Rent(capacity);
+ }
+
private byte[]? _rented;
private int _count;
public int Count
@@ -26,8 +36,14 @@ public int Count
public int Capacity => _rented?.Length ?? 0;
+ ///
+ /// The underlying buffer, including unused space at the end.
+ ///
public Span BufferSpan => _rented ?? default;
+ ///
+ /// A span of the used portion of the buffer.
+ ///
public Span Span => _count == 0 ? default : BufferSpan.Slice(0, _count);
public void Add(byte value)
@@ -54,19 +70,21 @@ private void AddSlow(byte value)
public void AddRange(ReadOnlySpan span)
{
- if (!span.IsEmpty)
- {
- var buffer = BufferSpan;
- var count = Count;
- if (buffer.Length - count < span.Length)
- {
- EnsureCapacity(checked(count + span.Length));
- buffer = BufferSpan;
- }
+ var buffer = GetAppendSpan(span.Length);
+ span.CopyTo(buffer);
+ Count += span.Length;
+ }
- span.CopyTo(buffer[count..]);
- Count = count + span.Length;
- }
+ ///
+ /// Get a span into the buffer that starts at the end of the current
+ /// buffer and is large enough to hold the specified number of bytes.
+ /// The is not updated.
+ ///
+ public Span GetAppendSpan(int size)
+ {
+ var count = Count;
+ EnsureCapacity(count + size);
+ return BufferSpan.Slice(count, size);
}
public void EnsureCapacity(int capacity)
diff --git a/src/Serde.MsgPack.csproj b/src/Serde.MsgPack.csproj
index 5436206..7c0a8b9 100644
--- a/src/Serde.MsgPack.csproj
+++ b/src/Serde.MsgPack.csproj
@@ -4,11 +4,11 @@
net8.0
enable
enable
- 0.1.0-preview1
+ 0.1.0-preview2
-
+
diff --git a/src/reader/MsgPackReader.Collections.cs b/src/reader/MsgPackReader.Collections.cs
new file mode 100644
index 0000000..7036829
--- /dev/null
+++ b/src/reader/MsgPackReader.Collections.cs
@@ -0,0 +1,134 @@
+
+namespace Serde.MsgPack;
+
+partial class MsgPackReader
+{
+ private struct DeserializeCollection(MsgPackReader deserializer, bool isDict, int length) : ITypeDeserializer
+ {
+ private int _index;
+ int? ITypeDeserializer.SizeOpt => isDict switch {
+ true => length / 2,
+ false => length,
+ };
+
+ bool ITypeDeserializer.ReadBool(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadBool();
+ _index++;
+ return v;
+ }
+
+ char ITypeDeserializer.ReadChar(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadChar();
+ _index++;
+ return v;
+ }
+
+ decimal ITypeDeserializer.ReadDecimal(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadDecimal();
+ _index++;
+ return v;
+ }
+
+ float ITypeDeserializer.ReadF32(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadF32();
+ _index++;
+ return v;
+ }
+
+ double ITypeDeserializer.ReadF64(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadF64();
+ _index++;
+ return v;
+ }
+
+ short ITypeDeserializer.ReadI16(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadI16();
+ _index++;
+ return v;
+ }
+
+ int ITypeDeserializer.ReadI32(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadI32();
+ _index++;
+ return v;
+ }
+
+ long ITypeDeserializer.ReadI64(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadI64();
+ _index++;
+ return v;
+ }
+
+ sbyte ITypeDeserializer.ReadI8(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadI8();
+ _index++;
+ return v;
+ }
+
+ string ITypeDeserializer.ReadString(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadString();
+ _index++;
+ return v;
+ }
+
+ ushort ITypeDeserializer.ReadU16(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadU16();
+ _index++;
+ return v;
+ }
+
+ uint ITypeDeserializer.ReadU32(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadU32();
+ _index++;
+ return v;
+ }
+
+ ulong ITypeDeserializer.ReadU64(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadU64();
+ _index++;
+ return v;
+ }
+
+ byte ITypeDeserializer.ReadU8(ISerdeInfo info, int index)
+ {
+ var v = deserializer.ReadU8();
+ _index++;
+ return v;
+ }
+
+ int ITypeDeserializer.TryReadIndex(ISerdeInfo info, out string? errorName)
+ {
+ errorName = null;
+ if (_index >= length)
+ {
+ return ITypeDeserializer.EndOfType;
+ }
+ return _index;
+ }
+
+ T ITypeDeserializer.ReadValue(ISerdeInfo info, int index, IDeserialize d)
+ {
+ var next = d.Deserialize(deserializer);
+ _index++;
+ return next;
+ }
+
+ void ITypeDeserializer.SkipValue(ISerdeInfo info, int index)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/reader/MsgPackReader.IDeserializeCollection.cs b/src/reader/MsgPackReader.IDeserializeCollection.cs
deleted file mode 100644
index 1b77af9..0000000
--- a/src/reader/MsgPackReader.IDeserializeCollection.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-
-namespace Serde.MsgPack;
-
-partial class MsgPackReader
-{
- private struct DeserializeCollection(MsgPackReader deserializer, bool isDict, int length) : IDeserializeCollection
- {
- private int _index;
- int? IDeserializeCollection.SizeOpt => isDict switch {
- true => length / 2,
- false => length,
- };
-
- bool IDeserializeCollection.TryReadValue(ISerdeInfo typeInfo, D d, out T next)
- {
- if (_index >= length)
- {
- next = default!;
- return false;
- }
- next = d.Deserialize(deserializer);
- _index++;
- return true;
- }
- }
-}
\ No newline at end of file
diff --git a/src/reader/MsgPackReader.IDeserializeType.cs b/src/reader/MsgPackReader.IDeserializeType.cs
deleted file mode 100644
index 9242f35..0000000
--- a/src/reader/MsgPackReader.IDeserializeType.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-
-namespace Serde.MsgPack;
-
-partial class MsgPackReader
-{
- private struct DeserializeType(MsgPackReader deserializer) : IDeserializeType
- {
- private int _index;
-
- bool IDeserializeType.ReadBool(int index) => deserializer.ReadBool();
-
- byte IDeserializeType.ReadByte(int index) => deserializer.ReadByte();
-
- char IDeserializeType.ReadChar(int index) => (char)deserializer.ReadU16();
-
- decimal IDeserializeType.ReadDecimal(int index) => deserializer.ReadDecimal();
- double IDeserializeType.ReadDouble(int index) => deserializer.ReadDouble();
-
- float IDeserializeType.ReadFloat(int index) => deserializer.ReadFloat();
-
- short IDeserializeType.ReadI16(int index) => deserializer.ReadI16();
-
- int IDeserializeType.ReadI32(int index) => deserializer.ReadI32();
-
- long IDeserializeType.ReadI64(int index) => deserializer.ReadI64();
-
- sbyte IDeserializeType.ReadSByte(int index) => deserializer.ReadSByte();
-
- string IDeserializeType.ReadString(int index) => deserializer.ReadString();
-
- ushort IDeserializeType.ReadU16(int index) => deserializer.ReadU16();
-
- uint IDeserializeType.ReadU32(int index) => deserializer.ReadU32();
-
- ulong IDeserializeType.ReadU64(int index) => deserializer.ReadU64();
-
- T IDeserializeType.ReadValue(int index, Serde.IDeserialize deserialize)
- => deserialize.Deserialize(deserializer);
-
- void IDeserializeType.SkipValue()
- => throw new NotImplementedException();
-
- int IDeserializeType.TryReadIndex(ISerdeInfo map, out string? errorName)
- {
- // Two options: we have a struct/class, or an enum
- if (map.Kind == InfoKind.CustomType)
- {
- // custom types always serialize in order, so just increment the index counter
- if (_index >= map.FieldCount)
- {
- errorName = null;
- return IDeserializeType.EndOfType;
- }
- errorName = null;
- return _index++;
- }
- else if (map.Kind == InfoKind.Enum)
- {
- // Enums are serialized as the index of the enum member
- errorName = null;
- return deserializer.ReadI32();
- }
- else
- {
- errorName = "Expected a custom type or enum, found: " + map.Kind;
- return IDeserializeType.IndexNotFound;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/reader/MsgPackReader.ITypeDeserializer.cs b/src/reader/MsgPackReader.ITypeDeserializer.cs
new file mode 100644
index 0000000..6b6bb02
--- /dev/null
+++ b/src/reader/MsgPackReader.ITypeDeserializer.cs
@@ -0,0 +1,75 @@
+
+namespace Serde.MsgPack;
+
+partial class MsgPackReader
+{
+ private struct DeserializeType(MsgPackReader deserializer) : ITypeDeserializer
+ {
+ int? ITypeDeserializer.SizeOpt => null;
+
+ private int _count;
+
+ bool ITypeDeserializer.ReadBool(ISerdeInfo info, int index) => deserializer.ReadBool();
+
+ byte ITypeDeserializer.ReadU8(ISerdeInfo info, int index) => deserializer.ReadU8();
+
+ char ITypeDeserializer.ReadChar(ISerdeInfo info, int index) => (char)deserializer.ReadU16();
+
+ decimal ITypeDeserializer.ReadDecimal(ISerdeInfo info, int index) => deserializer.ReadDecimal();
+ double ITypeDeserializer.ReadF64(ISerdeInfo info, int index) => deserializer.ReadF64();
+
+ float ITypeDeserializer.ReadF32(ISerdeInfo info, int index) => deserializer.ReadF32();
+
+ short ITypeDeserializer.ReadI16(ISerdeInfo info, int index) => deserializer.ReadI16();
+
+ int ITypeDeserializer.ReadI32(ISerdeInfo info, int index) => deserializer.ReadI32();
+
+ long ITypeDeserializer.ReadI64(ISerdeInfo info, int index) => deserializer.ReadI64();
+
+ sbyte ITypeDeserializer.ReadI8(ISerdeInfo info, int index) => deserializer.ReadI8();
+
+ string ITypeDeserializer.ReadString(ISerdeInfo info, int index) => deserializer.ReadString();
+
+ ushort ITypeDeserializer.ReadU16(ISerdeInfo info, int index) => deserializer.ReadU16();
+
+ uint ITypeDeserializer.ReadU32(ISerdeInfo info, int index) => deserializer.ReadU32();
+
+ ulong ITypeDeserializer.ReadU64(ISerdeInfo info, int index) => deserializer.ReadU64();
+
+ T ITypeDeserializer.ReadValue(ISerdeInfo info, int index, IDeserialize deserialize)
+ => deserialize.Deserialize(deserializer);
+
+ void ITypeDeserializer.SkipValue(ISerdeInfo info, int index)
+ => throw new NotImplementedException();
+
+ int ITypeDeserializer.TryReadIndex(ISerdeInfo map, out string? errorName)
+ {
+ // Two options: we have a struct/class, or an enum
+ if (map.Kind == InfoKind.CustomType)
+ {
+ if (_count >= map.FieldCount)
+ {
+ errorName = null;
+ return ITypeDeserializer.EndOfType;
+ }
+ // custom types are serialized like maps with field names as keys
+ var span = deserializer.ReadUtf8Span();
+ int index = map.TryGetIndex(span);
+ errorName = index == ITypeDeserializer.IndexNotFound ? span.ToString() : null;
+ _count++;
+ return index;
+ }
+ else if (map.Kind == InfoKind.Enum)
+ {
+ // Enums are serialized as the index of the enum member
+ errorName = null;
+ return deserializer.ReadI32();
+ }
+ else
+ {
+ errorName = "Expected a custom type or enum, found: " + map.Kind;
+ return ITypeDeserializer.IndexNotFound;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/reader/MsgPackReader.cs b/src/reader/MsgPackReader.cs
index d14bda5..8946973 100644
--- a/src/reader/MsgPackReader.cs
+++ b/src/reader/MsgPackReader.cs
@@ -30,11 +30,6 @@ private static void ThrowEof()
throw new Exception("Unexpected end of stream");
}
- T IDeserializer.ReadAny(IDeserializeVisitor v)
- {
- throw new NotImplementedException();
- }
-
bool IDeserializer.ReadBool() => ReadBool();
private bool ReadBool()
@@ -81,7 +76,7 @@ private byte EatByteOrThrow()
///
/// Eats at least one byte from the buffer.
///
- bool TryReadByte(out byte result)
+ bool TryReadU8(out byte result)
{
return TryReadByte(EatByteOrThrow(), out result);
}
@@ -102,27 +97,27 @@ bool TryReadByte(byte b, out byte result)
return false;
}
- byte IDeserializer.ReadByte() => ReadByte();
+ byte IDeserializer.ReadU8() => ReadU8();
- private byte ReadByte()
+ private byte ReadU8()
{
- if (!TryReadByte(out var b))
+ if (!TryReadU8(out var b))
{
throw new Exception($"Expected byte 0xcc, got 0x{b:x}");
}
return b;
}
- char IDeserializer.ReadChar()
+ public char ReadChar()
{
// char is encoded as either a 1-2 byte integer
return (char)ReadU16();
}
- IDeserializeCollection IDeserializer.ReadCollection(ISerdeInfo typeInfo)
+ private ITypeDeserializer ReadCollection(ISerdeInfo typeInfo)
{
var b = EatByteOrThrow();
- if (typeInfo.Kind == InfoKind.Enumerable)
+ if (typeInfo.Kind == InfoKind.List)
{
int length;
if (b <= 0x9f)
@@ -166,7 +161,7 @@ IDeserializeCollection IDeserializer.ReadCollection(ISerdeInfo typeInfo)
}
else
{
- throw new Exception("Expected collection");
+ throw new Exception("Expected either List or Dictionary, found " + typeInfo.Kind);
}
}
@@ -175,7 +170,7 @@ public decimal ReadDecimal()
throw new NotImplementedException();
}
- private double ReadDouble()
+ private double ReadF64()
{
var span = _reader.Span;
if (span.Length < 9)
@@ -192,9 +187,9 @@ private double ReadDouble()
return result;
}
- double IDeserializer.ReadDouble() => ReadDouble();
+ double IDeserializer.ReadF64() => ReadF64();
- private float ReadFloat()
+ private float ReadF32()
{
var span = _reader.Span;
if (span.Length < 5)
@@ -210,9 +205,9 @@ private float ReadFloat()
_reader.Advance(5);
return result;
}
- float IDeserializer.ReadFloat() => ReadFloat();
+ float IDeserializer.ReadF32() => ReadF32();
- private bool TryReadSbyte(out sbyte s)
+ private bool TryReadI8(out sbyte s)
{
var first = EatByteOrThrow();
if (first <= 0x7f || first >= 0xe0)
@@ -231,7 +226,7 @@ private bool TryReadSbyte(out sbyte s)
private bool TryReadI16(out short i16)
{
- if (TryReadSbyte(out var sb))
+ if (TryReadI8(out var sb))
{
i16 = sb;
return true;
@@ -284,7 +279,7 @@ private int ReadI32()
{
if (!TryReadI32(out var i32))
{
- throw new Exception("Expected 32-bit integer");
+ throw new Exception($"Expected 32-bit integer, found 0x{i32:x}");
}
return i32;
}
@@ -323,7 +318,7 @@ private long ReadI64()
long IDeserializer.ReadI64() => ReadI64();
- T? IDeserializer.ReadNullableRef(TProxy proxy)
+ T? IDeserializer.ReadNullableRef(IDeserialize proxy)
where T : class
{
var b = PeekByteOrThrow();
@@ -335,9 +330,9 @@ private long ReadI64()
return proxy.Deserialize(this);
}
- public sbyte ReadSByte()
+ public sbyte ReadI8()
{
- if (!TryReadSbyte(out var sb))
+ if (!TryReadI8(out var sb))
{
throw new Exception("Expected signed byte");
}
@@ -349,6 +344,13 @@ public sbyte ReadSByte()
private string ReadString()
{
// strings are encoded in UTF8 as byte arrays
+ var span = ReadUtf8Span();
+ var str = Encoding.UTF8.GetString(span);
+ return str;
+ }
+
+ private ReadOnlySpan ReadUtf8Span()
+ {
var b = EatByteOrThrow();
int length;
if (b <= 0xbf)
@@ -379,15 +381,17 @@ private string ReadString()
{
span = RefillNoEof(length);
}
- var str = Encoding.UTF8.GetString(span[..length]);
_reader.Advance(length);
- return str;
+ return span[..length];
}
- IDeserializeType IDeserializer.ReadType(ISerdeInfo typeInfo)
+ ITypeDeserializer IDeserializer.ReadType(ISerdeInfo typeInfo)
{
- // Types are just an array of fields
- if (typeInfo.Kind == InfoKind.CustomType)
+ if (typeInfo.Kind == InfoKind.List || typeInfo.Kind == InfoKind.Dictionary)
+ {
+ return ReadCollection(typeInfo);
+ }
+ else if (typeInfo.Kind == InfoKind.CustomType)
{
var fieldCount = typeInfo.FieldCount;
var b = EatByteOrThrow();
@@ -442,7 +446,7 @@ private ushort ReadU16()
private bool TryReadU16(out ushort u16)
{
- if (TryReadByte(out var b))
+ if (TryReadU8(out var b))
{
u16 = b;
return true;
diff --git a/src/writer/MsgPackWriter.EnumSerializer.cs b/src/writer/MsgPackWriter.EnumSerializer.cs
new file mode 100644
index 0000000..0be0867
--- /dev/null
+++ b/src/writer/MsgPackWriter.EnumSerializer.cs
@@ -0,0 +1,39 @@
+
+using System.Runtime.CompilerServices;
+
+namespace Serde.MsgPack;
+
+partial class MsgPackWriter
+{
+ private sealed class EnumSerializer(MsgPackWriter writer) : ITypeSerializer
+ {
+ public void End(ISerdeInfo info)
+ {
+ // Enums aren't nested, so no action needed
+ }
+
+ private void ThrowInvalidEnum([CallerMemberName] string memberName = "")
+ {
+ throw new InvalidOperationException($"EnumSerializer cannot be used for non-enum types. Called from {memberName}.");
+ }
+
+ public void WriteBool(ISerdeInfo typeInfo, int index, bool b) => ThrowInvalidEnum();
+ public void WriteChar(ISerdeInfo typeInfo, int index, char c) => ThrowInvalidEnum();
+ public void WriteDecimal(ISerdeInfo typeInfo, int index, decimal d) => ThrowInvalidEnum();
+ public void WriteF64(ISerdeInfo typeInfo, int index, double d) => ThrowInvalidEnum();
+ public void WriteValue(ISerdeInfo typeInfo, int index, T value, ISerialize serialize)
+ where T : class?
+ => ThrowInvalidEnum();
+ public void WriteF32(ISerdeInfo typeInfo, int index, float f) => ThrowInvalidEnum();
+ public void WriteI8(ISerdeInfo typeInfo, int index, sbyte b) => writer.WriteI8(b);
+ public void WriteI16(ISerdeInfo typeInfo, int index, short i16) => writer.WriteI16(i16);
+ public void WriteI32(ISerdeInfo typeInfo, int index, int i32) => writer.WriteI32(i32);
+ public void WriteI64(ISerdeInfo typeInfo, int index, long i64) => writer.WriteI64(i64);
+ public void WriteNull(ISerdeInfo typeInfo, int index) => ThrowInvalidEnum();
+ public void WriteString(ISerdeInfo typeInfo, int index, string s) => ThrowInvalidEnum();
+ public void WriteU8(ISerdeInfo typeInfo, int index, byte b) => writer.WriteU8(b);
+ public void WriteU16(ISerdeInfo typeInfo, int index, ushort u16) => writer.WriteU16(u16);
+ public void WriteU32(ISerdeInfo typeInfo, int index, uint u32) => writer.WriteU32(u32);
+ public void WriteU64(ISerdeInfo typeInfo, int index, ulong u64) => writer.WriteU64(u64);
+ }
+}
\ No newline at end of file
diff --git a/src/writer/MsgPackWriter.ISerializeCollection.cs b/src/writer/MsgPackWriter.ISerializeCollection.cs
index b24b77b..3f0b815 100644
--- a/src/writer/MsgPackWriter.ISerializeCollection.cs
+++ b/src/writer/MsgPackWriter.ISerializeCollection.cs
@@ -1,15 +1,62 @@
namespace Serde.MsgPack;
-partial class MsgPackWriter : ISerializeCollection
+partial class MsgPackWriter
{
- void ISerializeCollection.End(ISerdeInfo typeInfo)
+ private sealed class SerCollection(MsgPackWriter writer) : ITypeSerializer
{
- // No action needed, all collections are length-prefixed
- }
+ public void End(ISerdeInfo typeInfo)
+ {
+ // No action needed, all collections are length-prefixed
+ }
- void ISerializeCollection.SerializeElement(T value, U serialize)
- {
- serialize.Serialize(value, this);
+ public void WriteBool(ISerdeInfo typeInfo, int index, bool b)
+ => writer.WriteBool(b);
+
+ public void WriteChar(ISerdeInfo typeInfo, int index, char c)
+ => writer.WriteChar(c);
+
+ public void WriteDecimal(ISerdeInfo typeInfo, int index, decimal d)
+ => writer.WriteDecimal(d);
+
+ public void WriteF32(ISerdeInfo typeInfo, int index, float f)
+ => writer.WriteF32(f);
+
+ public void WriteF64(ISerdeInfo typeInfo, int index, double d)
+ => writer.WriteF64(d);
+
+ public void WriteI16(ISerdeInfo typeInfo, int index, short i16)
+ => writer.WriteI16(i16);
+
+ public void WriteI32(ISerdeInfo typeInfo, int index, int i32)
+ => writer.WriteI32(i32);
+
+ public void WriteI64(ISerdeInfo typeInfo, int index, long i64)
+ => writer.WriteI64(i64);
+
+ public void WriteI8(ISerdeInfo typeInfo, int index, sbyte b)
+ => writer.WriteI8(b);
+
+ public void WriteNull(ISerdeInfo typeInfo, int index)
+ => writer.WriteNull();
+
+ public void WriteString(ISerdeInfo typeInfo, int index, string s)
+ => writer.WriteString(s);
+
+ public void WriteU16(ISerdeInfo typeInfo, int index, ushort u16)
+ => writer.WriteU16(u16);
+
+ public void WriteU32(ISerdeInfo typeInfo, int index, uint u32)
+ => writer.WriteU32(u32);
+
+ public void WriteU64(ISerdeInfo typeInfo, int index, ulong u64)
+ => writer.WriteU64(u64);
+
+ public void WriteU8(ISerdeInfo typeInfo, int index, byte b)
+ => writer.WriteU8(b);
+
+ public void WriteValue(ISerdeInfo typeInfo, int index, T value, ISerialize serialize)
+ where T : class?
+ => serialize.Serialize(value, writer);
}
}
\ No newline at end of file
diff --git a/src/writer/MsgPackWriter.ISerializeType.cs b/src/writer/MsgPackWriter.ISerializeType.cs
index aec0bd6..baccd46 100644
--- a/src/writer/MsgPackWriter.ISerializeType.cs
+++ b/src/writer/MsgPackWriter.ISerializeType.cs
@@ -3,15 +3,112 @@
namespace Serde.MsgPack;
-partial class MsgPackWriter : ISerializeType
+partial class MsgPackWriter : ITypeSerializer
{
- void ISerializeType.End()
+ private void WritePropertyName(ISerdeInfo typeInfo, int fieldIndex)
{
- // No action needed, all collections are length-prefixed
+ var fieldName = typeInfo.GetFieldName(fieldIndex);
+ WriteUtf8(fieldName);
}
- void ISerializeType.SerializeField(ISerdeInfo typeInfo, int index, T value, U serialize)
+ void ITypeSerializer.End(ISerdeInfo typeInfo)
{
+ // No end, all types are length-prefixed
+ }
+
+ void ITypeSerializer.WriteValue(ISerdeInfo typeInfo, int fieldIndex, T value, ISerialize serialize)
+ {
+ WritePropertyName(typeInfo, fieldIndex);
serialize.Serialize(value, this);
}
+
+ void ITypeSerializer.WriteBool(ISerdeInfo typeInfo, int index, bool b)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteBool(b);
+ }
+
+ void ITypeSerializer.WriteChar(ISerdeInfo typeInfo, int index, char c)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteChar(c);
+ }
+
+ void ITypeSerializer.WriteU8(ISerdeInfo typeInfo, int index, byte b)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteU8(b);
+ }
+
+ void ITypeSerializer.WriteU16(ISerdeInfo typeInfo, int index, ushort u16)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteU16(u16);
+ }
+
+ void ITypeSerializer.WriteU32(ISerdeInfo typeInfo, int index, uint u32)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteU32(u32);
+ }
+
+ void ITypeSerializer.WriteU64(ISerdeInfo typeInfo, int index, ulong u64)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteU64(u64);
+ }
+
+ void ITypeSerializer.WriteI8(ISerdeInfo typeInfo, int index, sbyte b)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteI8(b);
+ }
+
+ void ITypeSerializer.WriteI16(ISerdeInfo typeInfo, int index, short i16)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteI16(i16);
+ }
+
+ void ITypeSerializer.WriteI32(ISerdeInfo typeInfo, int index, int i32)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteI32(i32);
+ }
+
+ void ITypeSerializer.WriteI64(ISerdeInfo typeInfo, int index, long i64)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteI64(i64);
+ }
+
+ void ITypeSerializer.WriteF32(ISerdeInfo typeInfo, int index, float f)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteF32(f);
+ }
+
+ void ITypeSerializer.WriteF64(ISerdeInfo typeInfo, int index, double d)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteF64(d);
+ }
+
+ void ITypeSerializer.WriteDecimal(ISerdeInfo typeInfo, int index, decimal d)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteDecimal(d);
+ }
+
+ void ITypeSerializer.WriteString(ISerdeInfo typeInfo, int index, string s)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteString(s);
+ }
+
+ void ITypeSerializer.WriteNull(ISerdeInfo typeInfo, int index)
+ {
+ WritePropertyName(typeInfo, index);
+ WriteNull();
+ }
}
\ No newline at end of file
diff --git a/src/writer/MsgPackWriter.cs b/src/writer/MsgPackWriter.cs
index 85175f5..33db05d 100644
--- a/src/writer/MsgPackWriter.cs
+++ b/src/writer/MsgPackWriter.cs
@@ -1,30 +1,38 @@
using System.Buffers.Binary;
using System.ComponentModel;
+using System.Runtime.CompilerServices;
using System.Text;
namespace Serde.MsgPack;
-internal sealed partial class MsgPackWriter(ScratchBuffer outBuffer) : ISerializer
+internal sealed partial class MsgPackWriter : ISerializer
{
- private readonly ScratchBuffer _out = outBuffer;
+ private readonly ScratchBuffer _out;
+ private readonly EnumSerializer _enumSerializer;
- void ISerializer.SerializeBool(bool b)
+ public MsgPackWriter(ScratchBuffer scratch)
+ {
+ _out = scratch;
+ _enumSerializer = new EnumSerializer(this);
+ }
+
+ public void WriteBool(bool b)
{
_out.Add(b ? (byte)0xc3 : (byte)0xc2);
}
- void ISerializer.SerializeByte(byte b) => SerializeU64(b);
+ public void WriteU8(byte b) => WriteU64(b);
- void ISerializer.SerializeChar(char c) => SerializeU64(c);
+ public void WriteChar(char c) => WriteU64(c);
- ISerializeCollection ISerializer.SerializeCollection(ISerdeInfo typeInfo, int? length)
+ ITypeSerializer ISerializer.WriteCollection(ISerdeInfo typeInfo, int? length)
{
if (length is null)
{
throw new InvalidOperationException("Cannot serialize a collection with an unknown length.");
}
- if (typeInfo.Kind == InfoKind.Enumerable)
+ if (typeInfo.Kind == InfoKind.List)
{
if (length <= 15)
{
@@ -43,62 +51,61 @@ ISerializeCollection ISerializer.SerializeCollection(ISerdeInfo typeInfo, int? l
}
else if (typeInfo.Kind == InfoKind.Dictionary)
{
- if (length <= 15)
- {
- _out.Add((byte)(0x80 | length));
- }
- else if (length <= 0xffff)
- {
- _out.Add(0xde);
- WriteBigEndian((ushort)length);
- }
- else
- {
- _out.Add(0xdf);
- WriteBigEndian((uint)length);
- }
+ WriteMapLength((int)length);
}
else
{
throw new InvalidOperationException("Expected a collection, found: " + typeInfo.Kind);
}
- return this;
+ return new SerCollection(this);
}
- void ISerializer.SerializeDecimal(decimal d)
+ private void WriteMapLength(int length)
{
- throw new NotImplementedException();
+ if (length <= 15)
+ {
+ _out.Add((byte)(0x80 | length));
+ }
+ else if (length <= 0xffff)
+ {
+ _out.Add(0xde);
+ WriteBigEndian((ushort)length);
+ }
+ else
+ {
+ _out.Add(0xdf);
+ WriteBigEndian((uint)length);
+ }
}
- void ISerializer.SerializeDouble(double d)
+ public void WriteDecimal(decimal d)
{
- _out.Add(0xcb);
- WriteBigEndian(d);
+ throw new NotImplementedException();
}
- void ISerializer.SerializeEnumValue(ISerdeInfo typeInfo, int index, T value, U serialize)
+ public void WriteF64(double d)
{
- // Serialize the index of the enum member
- SerializeI64(index);
+ _out.Add(0xcb);
+ WriteBigEndian(d);
}
- void ISerializer.SerializeFloat(float f)
+ public void WriteF32(float f)
{
_out.Add(0xca);
WriteBigEndian(f);
}
- void ISerializer.SerializeI16(short i16) => SerializeI64(i16);
+ public void WriteI16(short i16) => WriteI64(i16);
- void ISerializer.SerializeI32(int i32) => SerializeI64(i32);
+ public void WriteI32(int i32) => WriteI64(i32);
- void ISerializer.SerializeI64(long i64) => SerializeI64(i64);
+ void ISerializer.WriteI64(long i64) => WriteI64(i64);
- private void SerializeI64(long i64)
+ private void WriteI64(long i64)
{
if (i64 >= 0)
{
- SerializeU64((ulong)i64);
+ WriteU64((ulong)i64);
}
else if (i64 >= -32)
{
@@ -125,84 +132,104 @@ private void SerializeI64(long i64)
WriteBigEndian(i64);
}
}
-
- void ISerializer.SerializeNull()
+ public void WriteNull()
{
_out.Add(0xc0);
}
- void ISerializer.SerializeSByte(sbyte b) => SerializeI64(b);
+ public void WriteI8(sbyte b) => WriteI64(b);
- void ISerializer.SerializeString(string s)
+ private static readonly Encoding _utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
+
+ public void WriteString(string s)
{
- var bytes = Encoding.UTF8.GetBytes(s);
- if (bytes.Length <= 31)
+ // We can write the string directly to the output buffer, but the string
+ // is length-prefixed and we don't know precisely how long it will be until
+ // we encode it. So we need to write space for the length prefix first, then
+ // write the string, and finally go back, fill in the length prefix, and move
+ // the string if necessary.
+ var sLen = s.Length;
+ var maxByteCount = _utf8.GetMaxByteCount(sLen);
+ var appendSpan = _out.GetAppendSpan(checked(maxByteCount + 5 /* max length prefix */));
+ int estimatedOffset = sLen switch {
+ <= 31 => 1,
+ <= 255 => 2,
+ <= 65535 => 3,
+ _ => 5
+ };
+ var u8Dest = appendSpan.Slice(estimatedOffset, maxByteCount);
+ int actualStrSize = _utf8.GetBytes(s, u8Dest);
+ // write prefix and move body if necessary
+ int actualOffset = WriteUtf8Header(actualStrSize, appendSpan);
+ if (actualOffset < estimatedOffset)
{
- _out.Add((byte)(0xa0 | bytes.Length));
+ u8Dest.CopyTo(appendSpan.Slice(actualOffset, actualStrSize));
}
- else if (bytes.Length <= 0xff)
+ _out.Count += actualOffset + actualStrSize;
+ }
+
+ private void WriteUtf8(ReadOnlySpan str)
+ {
+ var span = _out.GetAppendSpan(str.Length + 5);
+ int offset = WriteUtf8Header(str.Length, span);
+ str.CopyTo(span.Slice(offset, str.Length));
+ _out.Count += offset + str.Length;
+ }
+
+ ///
+ /// Assumes that span is large enough to hold the header.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int WriteUtf8Header(int length, Span span)
+ {
+ int offset;
+ if (length <= 31)
{
- _out.Add(0xd9);
- _out.Add((byte)bytes.Length);
+ offset = 1;
+ span[0] = (byte)(0xa0 | length);
}
- else if (bytes.Length <= 0xffff)
+ else if (length <= 0xff)
{
- _out.Add(0xda);
- WriteBigEndian((ushort)bytes.Length);
+ offset = 2;
+ span[0] = 0xd9;
+ span[1] = unchecked((byte)length);
}
- else
+ else if (length <= 0xffff)
{
- _out.Add(0xdb);
- WriteBigEndian((uint)bytes.Length);
+ offset = 3;
+ span[0] = 0xda;
+ BinaryPrimitives.WriteUInt16BigEndian(span.Slice(1), (ushort)length);
}
- foreach (var b in bytes)
+ else
{
- _out.Add(b);
+ offset = 5;
+ span[0] = 0xdb;
+ BinaryPrimitives.WriteUInt32BigEndian(span.Slice(1), (uint)length);
}
+ return offset;
}
- ISerializeType ISerializer.SerializeType(ISerdeInfo typeInfo)
+ ITypeSerializer ISerializer.WriteType(ISerdeInfo typeInfo)
{
- // Check that, if the members are marked with [Key], they are in order.
- // We do not support out-of-order keys.
- for (int i = 0; i < typeInfo.FieldCount; i++)
+ switch (typeInfo.Kind)
{
- var attrs = typeInfo.GetFieldAttributes(i);
- foreach (var attr in attrs)
- {
- if (attr.AttributeType.FullName == "MessagePack.KeyAttribute")
- {
- if (attr.ConstructorArguments is [ { Value: int index } ] && index != i)
- {
- throw new InvalidOperationException($"Found member {typeInfo.GetFieldStringName(i)} declared at index {i} but marked with [Key({index})]. Key indices must match declaration order.");
- }
- }
- }
- }
-
- // Write as an array, with the keys left implicit in the order
- if (typeInfo.FieldCount <= 15)
- {
- _out.Add((byte)(0x90 | typeInfo.FieldCount));
- }
- else if (typeInfo.FieldCount <= 0xffff)
- {
- _out.Add(0xdc);
- WriteBigEndian((ushort)typeInfo.FieldCount);
- }
- else
- {
- _out.Add(0xdd);
- WriteBigEndian((uint)typeInfo.FieldCount);
+ case InfoKind.CustomType:
+ // Custom types are serialized as a map
+ WriteMapLength(typeInfo.FieldCount);
+ return this;
+ case InfoKind.Enum:
+ return _enumSerializer;
}
- return this;
+ throw new InvalidOperationException("Unexpected info kind: " + typeInfo.Kind);
}
- void ISerializer.SerializeU16(ushort u16) => SerializeU64(u16);
+ public void WriteU16(ushort u16) => WriteU64(u16);
+
+ public void WriteU32(uint u32) => WriteU64(u32);
- void ISerializer.SerializeU32(uint u32) => SerializeU64(u32);
+ void ISerializer.WriteU64(ulong u64) => WriteU64(u64);
- private void SerializeU64(ulong u64)
+ private void WriteU64(ulong u64)
{
if (u64 <= 0x7f)
{
@@ -230,47 +257,48 @@ private void SerializeU64(ulong u64)
}
}
- void ISerializer.SerializeU64(ulong u64) => SerializeU64(u64);
-
private void WriteBigEndian(ushort value)
{
- _out.Add((byte)(value >> 8));
- _out.Add((byte)value);
+ var span = _out.GetAppendSpan(2);
+ BinaryPrimitives.WriteUInt16BigEndian(
+ span,
+ value);
+ _out.Count += 2;
}
private void WriteBigEndian(uint value)
{
- _out.Add((byte)(value >> 24));
- _out.Add((byte)(value >> 16));
- _out.Add((byte)(value >> 8));
- _out.Add((byte)value);
+ var span = _out.GetAppendSpan(4);
+ BinaryPrimitives.WriteUInt32BigEndian(
+ span,
+ value
+ );
+ _out.Count += 4;
}
private void WriteBigEndian(ulong value)
{
- _out.Add((byte)(value >> 56));
- _out.Add((byte)(value >> 48));
- _out.Add((byte)(value >> 40));
- _out.Add((byte)(value >> 32));
- _out.Add((byte)(value >> 24));
- _out.Add((byte)(value >> 16));
- _out.Add((byte)(value >> 8));
- _out.Add((byte)value);
+ var span = _out.GetAppendSpan(8);
+ BinaryPrimitives.WriteUInt64BigEndian(
+ span,
+ value
+ );
+ _out.Count += 8;
}
private void WriteBigEndian(short value) => WriteBigEndian((ushort)value);
private void WriteBigEndian(int value) => WriteBigEndian((uint)value);
private void WriteBigEndian(long value) => WriteBigEndian((ulong)value);
- private void WriteBigEndian(float f)
+ private void WriteBigEndian(float value)
{
- Span bytes = stackalloc byte[4];
- BinaryPrimitives.WriteSingleBigEndian(bytes, f);
- _out.AddRange(bytes);
+ var span = _out.GetAppendSpan(4);
+ BinaryPrimitives.WriteSingleBigEndian(span, value);
+ _out.Count += 4;
}
- private void WriteBigEndian(double d)
+ private void WriteBigEndian(double value)
{
- Span bytes = stackalloc byte[8];
- BinaryPrimitives.WriteDoubleBigEndian(bytes, d);
- _out.AddRange(bytes);
+ var span = _out.GetAppendSpan(8);
+ BinaryPrimitives.WriteDoubleBigEndian(span, value);
+ _out.Count += 8;
}
}
\ No newline at end of file
diff --git a/tests/RoundTripTests.cs b/tests/RoundTripTests.cs
index 9992ee9..d46a839 100644
--- a/tests/RoundTripTests.cs
+++ b/tests/RoundTripTests.cs
@@ -14,38 +14,38 @@ public void TestChar()
[Fact]
public void TestByte()
{
- AssertRoundTrip((byte)42, ByteProxy.Instance);
- AssertRoundTrip((byte)0xf0, ByteProxy.Instance);
+ AssertRoundTrip((byte)42, U8Proxy.Instance);
+ AssertRoundTrip((byte)0xf0, U8Proxy.Instance);
}
[Fact]
public void TestByteSizedUInt()
{
- AssertRoundTrip(42u, UInt32Proxy.Instance);
+ AssertRoundTrip(42u, U32Proxy.Instance);
}
[Fact]
public void TestPositiveByteSizedInt()
{
- AssertRoundTrip(42, Int32Proxy.Instance);
+ AssertRoundTrip(42, I32Proxy.Instance);
}
[Fact]
public void TestNegativeByteSizedInt()
{
- AssertRoundTrip(-42, Int32Proxy.Instance);
+ AssertRoundTrip(-42, I32Proxy.Instance);
}
[Fact]
public void TestPositiveUInt16()
{
- AssertRoundTrip((ushort)0x1000, UInt16Proxy.Instance);
+ AssertRoundTrip((ushort)0x1000, U16Proxy.Instance);
}
[Fact]
public void TestNegativeInt16()
{
- AssertRoundTrip((short)-0x1000, Int16Proxy.Instance);
+ AssertRoundTrip((short)-0x1000, I16Proxy.Instance);
}
[Fact]
@@ -59,8 +59,8 @@ public void TestNullableString()
{
AssertRoundTrip<
string?,
- NullableRefProxy.Serialize,
- NullableRefProxy.Deserialize>(null);
+ NullableRefProxy.Ser,
+ NullableRefProxy.De>(null);
}
[GenerateSerde]
@@ -92,12 +92,11 @@ public void TestIntEnum()
}
[GenerateSerde]
- [MessagePackObject]
+ [SerdeTypeOptions(MemberFormat = MemberFormat.None)]
+ [MessagePackObject(keyAsPropertyName: true)]
public partial record Point
{
- [Key(0)]
public int X { get; init; }
- [Key(1)]
public int Y { get; init; }
}
@@ -117,7 +116,7 @@ public partial record OutOfOrderKeys
public int X { get; init; }
}
- [Fact]
+ [Fact(Skip = "Key ordering is not supported for Serde")]
public void TestOutOfOrderKeys()
{
// Out of order keys are not supported for Serde
@@ -131,9 +130,9 @@ public void TestOutOfOrderKeys()
[Fact]
public void TestDouble()
{
- AssertRoundTrip(3.14, DoubleProxy.Instance);
- AssertRoundTrip(double.NaN, DoubleProxy.Instance);
- AssertRoundTrip(double.PositiveInfinity, DoubleProxy.Instance);
+ AssertRoundTrip(3.14, F64Proxy.Instance);
+ AssertRoundTrip(double.NaN, F64Proxy.Instance);
+ AssertRoundTrip(double.PositiveInfinity, F64Proxy.Instance);
}
[Fact]
@@ -141,16 +140,16 @@ public void TestArray()
{
AssertRoundTrip<
int[],
- ArrayProxy.Serialize,
- ArrayProxy.Deserialize>(new[] { 1, 2, 3 });
+ ArrayProxy.Ser,
+ ArrayProxy.De>(new[] { 1, 2, 3 });
AssertRoundTrip<
string[],
- ArrayProxy.Serialize,
- ArrayProxy.Deserialize>(new[] { "a", "b", "c" });
+ ArrayProxy.Ser,
+ ArrayProxy.De>(new[] { "a", "b", "c" });
AssertRoundTrip<
Point[],
- ArrayProxy.Serialize,
- ArrayProxy.Deserialize>(
+ ArrayProxy.Ser,
+ ArrayProxy.De>(
new[] { new Point { X = 1, Y = 2 }, new Point { X = 3, Y = 4 } });
}
@@ -159,47 +158,47 @@ public void TestDictionary()
{
AssertRoundTrip<
Dictionary,
- DictProxy.Serialize,
- DictProxy.Deserialize>(
+ DictProxy.Ser,
+ DictProxy.De>(
new Dictionary { { "a", 1 }, { "b", 2 } });
AssertRoundTrip<
Dictionary,
- DictProxy.Serialize,
- DictProxy.Deserialize>(
+ DictProxy.Ser,
+ DictProxy.De>(
new Dictionary { { 1, "a" }, { 2, "b" } });
AssertRoundTrip<
Dictionary,
- DictProxy.Serialize,
- DictProxy.Deserialize>(
+ DictProxy.Ser,
+ DictProxy.De>(
new Dictionary { { new Point { X = 1, Y = 2 }, "a" }, { new Point { X = 3, Y = 4 }, "b" } });
}
[Fact]
public void TestFloat()
{
- AssertRoundTrip(3.14f, SingleProxy.Instance);
- AssertRoundTrip(float.NaN, SingleProxy.Instance);
- AssertRoundTrip(float.PositiveInfinity, SingleProxy.Instance);
+ AssertRoundTrip(3.14f, F32Proxy.Instance);
+ AssertRoundTrip(float.NaN, F32Proxy.Instance);
+ AssertRoundTrip(float.PositiveInfinity, F32Proxy.Instance);
}
private static void AssertRoundTrip(T expected)
where T : ISerializeProvider, IDeserializeProvider, IEquatable
{
- AssertRoundTrip(expected, T.SerializeInstance, T.DeserializeInstance);
+ AssertRoundTrip(expected, SerializeProvider.GetSerialize(), DeserializeProvider.GetDeserialize());
}
private static void AssertRoundTrip(T expected, TSerialize serializeObject)
where TSerialize : ISerialize, IDeserializeProvider
{
- AssertRoundTrip(expected, serializeObject, TSerialize.DeserializeInstance);
+ AssertRoundTrip(expected, serializeObject, TSerialize.Instance);
}
private static void AssertRoundTrip(T expected)
where TSerialize : ISerializeProvider
where TDeserialize : IDeserializeProvider
{
- var serialized = MsgPackSerializer.Serialize(expected, TSerialize.SerializeInstance);
- var actual = MsgPackSerializer.Deserialize>(serialized, TDeserialize.DeserializeInstance);
+ var serialized = MsgPackSerializer.Serialize(expected, TSerialize.Instance);
+ var actual = MsgPackSerializer.Deserialize>(serialized, TDeserialize.Instance);
Assert.Equal(expected, actual);
}
diff --git a/tests/Serde.MsgPack.Tests.csproj b/tests/Serde.MsgPack.Tests.csproj
index 250e0ed..1952564 100644
--- a/tests/Serde.MsgPack.Tests.csproj
+++ b/tests/Serde.MsgPack.Tests.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/tests/SerializeOracleTests.cs b/tests/SerializeOracleTests.cs
index e028641..e4b500b 100644
--- a/tests/SerializeOracleTests.cs
+++ b/tests/SerializeOracleTests.cs
@@ -10,8 +10,8 @@ public partial class SerializeOracleTests
[Fact]
public void TestByte()
{
- AssertMsgPackEqual((byte)42, ByteProxy.Instance);
- AssertMsgPackEqual((byte)0xf0, ByteProxy.Instance);
+ AssertMsgPackEqual((byte)42, U8Proxy.Instance);
+ AssertMsgPackEqual((byte)0xf0, U8Proxy.Instance);
}
[Fact]
@@ -23,31 +23,31 @@ public void TestChar()
[Fact]
public void TestByteSizedUInt()
{
- AssertMsgPackEqual(42u, UInt32Proxy.Instance);
+ AssertMsgPackEqual(42u, U32Proxy.Instance);
}
[Fact]
public void TestPositiveByteSizedInt()
{
- AssertMsgPackEqual(42, Int32Proxy.Instance);
+ AssertMsgPackEqual(42, I32Proxy.Instance);
}
[Fact]
public void TestNegativeByteSizedInt()
{
- AssertMsgPackEqual(-42, Int32Proxy.Instance);
+ AssertMsgPackEqual(-42, I32Proxy.Instance);
}
[Fact]
public void TestPositiveUInt16()
{
- AssertMsgPackEqual((ushort)0x1000, UInt16Proxy.Instance);
+ AssertMsgPackEqual((ushort)0x1000, U16Proxy.Instance);
}
[Fact]
public void TestNegativeInt16()
{
- AssertMsgPackEqual((short)-0x1000, Int16Proxy.Instance);
+ AssertMsgPackEqual((short)-0x1000, I16Proxy.Instance);
}
[Fact]
@@ -59,7 +59,7 @@ public void TestString()
[Fact]
public void TestNullableString()
{
- AssertMsgPackEqual((string?)null, NullableRefProxy.Serialize.Instance);
+ AssertMsgPackEqual((string?)null, NullableRefProxy.Ser.Instance);
}
[GenerateSerialize]
@@ -91,12 +91,11 @@ public void TestIntEnum()
}
[GenerateSerialize]
- [MessagePackObject]
+ [SerdeTypeOptions(MemberFormat = MemberFormat.None)]
+ [MessagePackObject(keyAsPropertyName: true)]
public partial record Point
{
- [Key(0)]
public int X { get; init; }
- [Key(1)]
public int Y { get; init; }
}
@@ -116,7 +115,7 @@ public partial record OutOfOrderKeys
public int X { get; init; }
}
- [Fact]
+ [Fact(Skip = "Keys are not supported for Serde")]
public void TestOutOfOrderKeys()
{
// Out of order keys are not supported for Serde
@@ -130,37 +129,37 @@ public void TestOutOfOrderKeys()
[Fact]
public void TestDouble()
{
- AssertMsgPackEqual(3.14, DoubleProxy.Instance);
- AssertMsgPackEqual(double.NaN, DoubleProxy.Instance);
- AssertMsgPackEqual(double.PositiveInfinity, DoubleProxy.Instance);
+ AssertMsgPackEqual(3.14, F64Proxy.Instance);
+ AssertMsgPackEqual(double.NaN, F64Proxy.Instance);
+ AssertMsgPackEqual(double.PositiveInfinity, F64Proxy.Instance);
}
[Fact]
public void TestArray()
{
- AssertMsgPackEqual(new[] { 1, 2, 3 }, ArrayProxy.Serialize.Instance);
- AssertMsgPackEqual(new[] { "a", "b", "c" }, ArrayProxy.Serialize.Instance);
+ AssertMsgPackEqual(new[] { 1, 2, 3 }, ArrayProxy.Ser.Instance);
+ AssertMsgPackEqual(new[] { "a", "b", "c" }, ArrayProxy.Ser.Instance);
AssertMsgPackEqual(new[] { new Point { X = 1, Y = 2 }, new Point { X = 3, Y = 4 } },
- ArrayProxy.Serialize.Instance);
+ ArrayProxy.Ser.Instance);
}
[Fact]
public void TestDictionary()
{
AssertMsgPackEqual(new Dictionary { { "a", 1 }, { "b", 2 } },
- DictProxy.Serialize.Instance);
+ DictProxy.Ser.Instance);
AssertMsgPackEqual(new Dictionary { { 1, "a" }, { 2, "b" } },
- DictProxy.Serialize.Instance);
+ DictProxy.Ser.Instance);
AssertMsgPackEqual(new Dictionary { { new Point { X = 1, Y = 2 }, "a" }, { new Point { X = 3, Y = 4 }, "b" } },
- DictProxy.Serialize.Instance);
+ DictProxy.Ser.Instance);
}
[Fact]
public void TestFloat()
{
- AssertMsgPackEqual(3.14f, SingleProxy.Instance);
- AssertMsgPackEqual(float.NaN, SingleProxy.Instance);
- AssertMsgPackEqual(float.PositiveInfinity, SingleProxy.Instance);
+ AssertMsgPackEqual(3.14f, F32Proxy.Instance);
+ AssertMsgPackEqual(float.NaN, F32Proxy.Instance);
+ AssertMsgPackEqual(float.PositiveInfinity, F32Proxy.Instance);
}
private void AssertMsgPackEqual(T value, U proxy)
@@ -172,6 +171,6 @@ private void AssertMsgPackEqual(T value, U proxy)
}
private void AssertMsgPackEqual(T value) where T : ISerializeProvider
- => AssertMsgPackEqual(value, T.SerializeInstance);
+ => AssertMsgPackEqual(value, T.Instance);
}