-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for ByteString data type (#371)
`ByteString` represents an immutable sequence of bytes. This commit adds support for creating `ByteString` instances from `byte[]` or `ReadOnlyMemory<byte>`, automatic conversion to/from `Variant`, and JSON serializer support.
- Loading branch information
1 parent
8947c6a
commit 97ecec8
Showing
12 changed files
with
338 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
using System; | ||
using System.Runtime.InteropServices; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace DataCore.Adapter.Common { | ||
|
||
/// <summary> | ||
/// <see cref="ByteString"/> represents an immutable sequence of bytes. | ||
/// </summary> | ||
[JsonConverter(typeof(ByteStringConverter))] | ||
public readonly struct ByteString : IEquatable<ByteString> { | ||
|
||
/// <summary> | ||
/// An empty <see cref="ByteString"/> instance. | ||
/// </summary> | ||
public static ByteString Empty => default; | ||
|
||
/// <summary> | ||
/// The underlying byte sequence. | ||
/// </summary> | ||
public ReadOnlyMemory<byte> Bytes { get; } | ||
|
||
/// <summary> | ||
/// The length of the byte sequence. | ||
/// </summary> | ||
public int Length => Bytes.Length; | ||
|
||
/// <summary> | ||
/// Specifies if the byte sequence is empty. | ||
/// </summary> | ||
public bool IsEmpty => Bytes.IsEmpty; | ||
|
||
|
||
/// <summary> | ||
/// Creates a new <see cref="ByteString"/> instance. | ||
/// </summary> | ||
/// <param name="bytes"> | ||
/// The byte sequence. | ||
/// </param> | ||
public ByteString(ReadOnlyMemory<byte> bytes) { | ||
Bytes = bytes; | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Creates a new <see cref="ByteString"/> instance. | ||
/// </summary> | ||
/// <param name="bytes"> | ||
/// The byte sequence. | ||
/// </param> | ||
public ByteString(byte[] bytes) { | ||
Bytes = bytes ?? Array.Empty<byte>(); | ||
} | ||
|
||
|
||
/// <inheritdoc/> | ||
public override string ToString() { | ||
if (Bytes.IsEmpty) { | ||
return string.Empty; | ||
} | ||
|
||
if (MemoryMarshal.TryGetArray(Bytes, out ArraySegment<byte> segment)) { | ||
return Convert.ToBase64String(segment.Array!, segment.Offset, segment.Count); | ||
} | ||
else { | ||
return Convert.ToBase64String(Bytes.ToArray()); | ||
} | ||
} | ||
|
||
|
||
/// <inheritdoc/> | ||
public override int GetHashCode() { | ||
// We need to calculate a hash code that distributes evenly across a hash space, but | ||
// we don't want to have to iterate over the entire byte sequence to do so. Therefore, | ||
// we will compute a hash code based on the following criteria: | ||
// | ||
// * Length of the byte sequence | ||
// * First byte (non-empty byte sequences only) | ||
// * Middle byte (non-empty byte sequences only) | ||
// * Last byte (non-empty byte sequences only) | ||
|
||
if (IsEmpty) { | ||
return HashCode.Combine(0); | ||
} | ||
|
||
return HashCode.Combine(Length, Bytes.Span[0], Bytes.Span[(Bytes.Span.Length - 1) / 2], Bytes.Span[Bytes.Span.Length - 1]); | ||
} | ||
|
||
|
||
/// <inheritdoc/> | ||
public override bool Equals(object obj) { | ||
return obj is ByteString other && Equals(other); | ||
} | ||
|
||
|
||
/// <inheritdoc/> | ||
public bool Equals(ByteString other) { | ||
return Length == other.Length && Bytes.Span.SequenceEqual(other.Bytes.Span); | ||
} | ||
|
||
|
||
/// <inheritdoc/> | ||
public static implicit operator ByteString(ReadOnlyMemory<byte> bytes) => new ByteString(bytes); | ||
|
||
/// <inheritdoc/> | ||
public static implicit operator ByteString(byte[] bytes) => new ByteString(bytes); | ||
|
||
/// <inheritdoc/> | ||
public static implicit operator ReadOnlyMemory<byte>(ByteString bytes) => bytes.Bytes; | ||
|
||
/// <inheritdoc/> | ||
public static implicit operator byte[](ByteString bytes) => bytes.Bytes.ToArray(); | ||
|
||
} | ||
|
||
|
||
/// <summary> | ||
/// JSON converter for <see cref="ByteString"/>. | ||
/// </summary> | ||
internal sealed class ByteStringConverter : JsonConverter<ByteString> { | ||
|
||
/// <inheritdoc/> | ||
public override ByteString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { | ||
if (reader.TokenType == JsonTokenType.Null) { | ||
return ByteString.Empty; | ||
} | ||
|
||
if (reader.TokenType != JsonTokenType.String) { | ||
throw new JsonException(); | ||
} | ||
|
||
return new ByteString(reader.GetBytesFromBase64()); | ||
} | ||
|
||
|
||
/// <inheritdoc/> | ||
public override void Write(Utf8JsonWriter writer, ByteString value, JsonSerializerOptions options) { | ||
if (value.IsEmpty) { | ||
writer.WriteNullValue(); | ||
return; | ||
} | ||
writer.WriteBase64StringValue(value.Bytes.Span); | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
src/DataCore.Adapter.Json.Newtonsoft/ByteStringConverter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using System; | ||
|
||
using DataCore.Adapter.Common; | ||
|
||
using Newtonsoft.Json; | ||
|
||
namespace DataCore.Adapter.NewtonsoftJson { | ||
|
||
/// <summary> | ||
/// JSON converter for <see cref="ByteString"/>. | ||
/// </summary> | ||
public class ByteStringConverter : JsonConverter<ByteString> { | ||
|
||
/// <inheritdoc/> | ||
public override void WriteJson(JsonWriter writer, ByteString value, JsonSerializer serializer) { | ||
if (value.IsEmpty) { | ||
writer.WriteNull(); | ||
return; | ||
} | ||
|
||
writer.WriteValue(value.Bytes.ToArray()); | ||
} | ||
|
||
|
||
/// <inheritdoc/> | ||
public override ByteString ReadJson(JsonReader reader, Type objectType, ByteString existingValue, bool hasExistingValue, JsonSerializer serializer) { | ||
if (reader.TokenType == JsonToken.Null) { | ||
return ByteString.Empty; | ||
} | ||
|
||
if (reader.TokenType != JsonToken.Bytes) { | ||
throw new JsonException(); | ||
} | ||
|
||
return new ByteString(reader.ReadAsBytes()!); | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
#nullable enable | ||
DataCore.Adapter.NewtonsoftJson.ByteStringConverter | ||
DataCore.Adapter.NewtonsoftJson.ByteStringConverter.ByteStringConverter() -> void | ||
DataCore.Adapter.NewtonsoftJson.JsonElementConverter.JsonElementConverter(System.Text.Json.JsonSerializerOptions? options) -> void | ||
DataCore.Adapter.NewtonsoftJson.NullableJsonElementConverter.NullableJsonElementConverter(System.Text.Json.JsonSerializerOptions? options) -> void | ||
override DataCore.Adapter.NewtonsoftJson.ByteStringConverter.ReadJson(Newtonsoft.Json.JsonReader! reader, System.Type! objectType, DataCore.Adapter.Common.ByteString existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer! serializer) -> DataCore.Adapter.Common.ByteString | ||
override DataCore.Adapter.NewtonsoftJson.ByteStringConverter.WriteJson(Newtonsoft.Json.JsonWriter! writer, DataCore.Adapter.Common.ByteString value, Newtonsoft.Json.JsonSerializer! serializer) -> void | ||
static DataCore.Adapter.NewtonsoftJson.JsonSerializerSettingsExtensions.AddDataCoreAdapterConverters(this Newtonsoft.Json.JsonSerializerSettings! settings, System.Text.Json.JsonSerializerOptions? jsonElementConverterOptions) -> void | ||
static DataCore.Adapter.NewtonsoftJson.JsonSerializerSettingsExtensions.AddDataCoreAdapterConverters(this System.Collections.Generic.ICollection<Newtonsoft.Json.JsonConverter!>! converters, System.Text.Json.JsonSerializerOptions? jsonElementConverterOptions) -> void |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.