@KrzysztofCwalina suggested to add a new type called BinaryData. The Azure SDK team found in user studies that people struggle with the various ways we represent binary data (span, memory, byte arrays, streams). They experimented with a new type BinaryData that is a struct-based wrapper around a byte[] and provides constructors, factory methods, and conversion methods to convert data from and to the various representations, including JSON serialization.
Full spec is here: https://github.com/dotnet/designs/pull/155
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.
System.Text.Json, with specific optionsSystem.Text.Json, but we acknowledge that defaults are very valuable. If we need to support other serializers, then we can add an enumextension statics we could make the type more low level than serializationSetBytes, SetObject) which will throw when the underlying array is non-null. That prevents mutation and uses a simple pattern var x = new BinaryData(); x.SetBytes(...);. And on top, we can use regular extension methods to split out JSON serialization.corlib because it would depend on System.Text.Json (and whatever future serializers it needs to support). Hence, most areas in the BCL wouldn't be able to take BinaryData, but technologies above the BCL could (Microsoft.Extensions, ASP.NET Core, Azure SDK etc)Stream.C#
namespace System
{
public readonly struct BinaryData
{
public BinaryData(ReadOnlySpan<byte> data);
public BinaryData(byte[] data);
public BinaryData(object jsonSerializable, Type? type = null);
public BinaryData(ReadOnlyMemory<byte> data);
public BinaryData(string data);
public static BinaryData FromBytes(ReadOnlyMemory<byte> data);
public static BinaryData FromBytes(ReadOnlySpan<byte> data);
public static BinaryData FromBytes(byte[] data);
public static BinaryData FromObject<T>(T jsonSerializable, CancellationToken cancellationToken = default);
public static Task<BinaryData> FromObjectAsync<T>(T jsonSerializable, CancellationToken cancellationToken = default);
public static BinaryData FromStream(Stream stream);
public static Task<BinaryData> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default);
public static BinaryData FromString(string data);
public static implicit operator ReadOnlyMemory<byte>(BinaryData data);
public ReadOnlyMemory<byte> ToBytes();
public T ToObject<T>(CancellationToken cancellationToken = default);
public ValueTask<T> ToObjectAsync<T>(CancellationToken cancellationToken = default);
public Stream ToStream();
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj);
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode();
public override string ToString();
}
}
I have a similar type in my own libraries and one thing I've found useful which isn't in this surface is base64 read and write capability.
ObjectSerializer abstraction, like Azure SDK has. This would solve the 3rd party extension problem in general.FromObject and ToObjectRegarding constructors vs factory methods, perhaps future tooling could suggest FactoryAttribute-ed static methods from a type when the user types new Whatever. (FactoryAttribute is/was being considered for use in a future version of C# where the compiler would require that an attributed method return a new instance.) Or even without FactoryAttribute, tooling could suggest static methods that return the containing type when the user types new Whatever.
I didn't see @carlreinke comment when I suggested #42314 but I think this is exactly what you were referring to
HttpClientFromObjectAsync and ToObjectAsync methods, they are hold over from the serialization abstraction which had async implementationsReadOnlyMemory<T>Equals(), GetHashCode()byte[] and ReadOnlyMemory<byte> will not copy; they just wrap the payload. Mutations will be observed.C#
namespace System
{
public class BinaryData
{
public BinaryData(byte[] data);
public BinaryData(object jsonSerializable, JsonSerializerOptions options = default, Type? type = null);
public BinaryData(ReadOnlyMemory<byte> data);
public BinaryData(string data);
public static BinaryData FromBytes(ReadOnlyMemory<byte> data);
public static BinaryData FromBytes(byte[] data);
public static BinaryData FromObjectAsJson<T>(T jsonSerializable, JsonSerializerOptions options = default, CancellationToken cancellationToken = default);
public static BinaryData FromStream(Stream stream);
public static Task<BinaryData> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default);
public static BinaryData FromString(string data);
public static implicit operator ReadOnlyMemory<byte>(BinaryData data);
public static implicit operator ReadOnlySpan<byte>(BinaryData data);
public ReadOnlyMemory<byte> ToBytes();
public T ToObjectFromJson<T>(JsonSerializerOptions options = default, CancellationToken cancellationToken = default);
public Stream ToStream();
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj);
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode();
public override string ToString();
}
}
I believe this should support ReadOnlySequence<byte>, too:
C#
namespace System
{
public class BinaryData
{
public BinaryData(ReadOnlySequence<byte> data);
public static BinaryData FromBytes(ReadOnlySequence<byte> data);
}
}
It seems like a layering violation that...
System refers to JSON as a conceptAs it stands, this type appears to be a DTO helper object hard-coded to use System.Text.Json. It would more cleanly fit into that namespace and package.
Most helpful comment
I have a similar type in my own libraries and one thing I've found useful which isn't in this surface is base64 read and write capability.