Skip to content

Commit

Permalink
CompositeKey and IEventDatFormatter
Browse files Browse the repository at this point in the history
  • Loading branch information
chullybun committed Jan 14, 2024
1 parent 342b161 commit 8f0d72d
Show file tree
Hide file tree
Showing 15 changed files with 535 additions and 144 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Represents the **NuGet** versions.
- *Enhancement*: A new `Abstractions.ServiceBusMessageActions` has been created to encapsulate either a `Microsoft.Azure.WebJobs.ServiceBus.ServiceBusMessageActions` (existing [_in-process_](https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-class-library) function support) or `Microsoft.Azure.Functions.Worker.ServiceBusMessageActions` (new [_isolated_](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide) function support) and used internally. Implicit conversion is enabled to simplify usage; existing projects will need to be recompiled. The latter capability does not support `RenewAsync` and as such this capability is no longer leveraged for consistency; review documented [`PeekLock`](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cextensionv5&pivots=programming-language-csharp#peeklock-behavior) behavior to get desired outcome.
- *Enhancement*: The `Result`, `Result<T>`, `PagingArgs` and `PagingResult` have had `IEquatable` added to enable equality comparisons.
- *Enhancement*: Upgraded `UnitTestEx` dependency to `4.0.2` to enable _isolated_ function testing.
- *Enhancement*: Enabled `IJsonSerializer` support for `CompositeKey` JSON serialization/deserialization.
- *Enhancement*: Added `IEventDataFormatter` which when implemented by the value set as the `EventData.Value` allows additional formatting to be applied by the `EventDataFormatter`.
- *Fixed*: `EventDataFormatter` and `CloudEventSerializerBase` updated to correctly set the `Key` where applicable.
- *Internal:* Upgraded `NUnit` dependency to `4.0.1` for all `CoreEx` unit test; also, all unit tests now leverage the [_NUnit constraint model_](https://docs.nunit.org/articles/nunit/writing-tests/assertions/assertion-models/constraint.html) testing approach.

## v3.8.1
Expand Down
147 changes: 147 additions & 0 deletions src/CoreEx.Newtonsoft/Json/CompositeKeyJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx

using CoreEx.Entities;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CoreEx.Newtonsoft.Json
{
/// <summary>
/// Performs JSON value conversion for <see cref="CompositeKey"/> values.
/// </summary>
public class CompositeKeyJsonConverter : JsonConverter
{
/// <inheritdoc/>
public override bool CanConvert(Type objectType) => objectType == typeof(CompositeKey);

/// <inheritdoc/>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return CompositeKey.Empty;

if (reader.TokenType != JsonToken.StartArray)
{
var jtr = (JsonTextReader)reader;
throw new JsonSerializationException($"Expected {nameof(JsonToken.StartArray)} for a {nameof(CompositeKey)}; found {reader.TokenType}.", jtr.Path, jtr.LineNumber, jtr.LinePosition, null);
}

var depth = reader.Depth;
var args = new List<object?>();

reader.Read();
while (reader.Depth > depth)
{
if (reader.TokenType == JsonToken.Null)
{
args.Add(null);
reader.Read();
continue;
}

if (reader.TokenType != JsonToken.StartObject)
{
var jtr = (JsonTextReader)reader;
throw new JsonSerializationException($"Expected {nameof(JsonToken.StartObject)} for a {nameof(CompositeKey)}; found {reader.TokenType}.", jtr.Path, jtr.LineNumber, jtr.LinePosition, null);
}

var objDepth = reader.Depth;
reader.Read();
while (reader.Depth > objDepth)
{
if (reader.TokenType != JsonToken.PropertyName)
{
var jtr = (JsonTextReader)reader;
throw new JsonSerializationException($"Expected {nameof(JsonToken.PropertyName)} for a {nameof(CompositeKey)}; found {reader.TokenType}.", jtr.Path, jtr.LineNumber, jtr.LinePosition, null);
}

var name = reader.Value;

switch (name)
{
case "string": args.Add(reader.ReadAsString()); break;
case "char": args.Add(reader.ReadAsString()?.ToCharArray().FirstOrDefault()); break;
case "short": args.Add((short?)reader.ReadAsInt32()); break;
case "int": args.Add(reader.ReadAsInt32()); break;
case "long": args.Add((long?)reader.ReadAsDecimal()); break;
case "guid": args.Add(reader.ReadAsString() is string s && Guid.TryParse(s, out var g) ? g : null); break;
case "datetime": args.Add(reader.ReadAsDateTime()); break;
case "datetimeoffset": args.Add(reader.ReadAsDateTimeOffset()); break;
case "ushort": args.Add((ushort?)reader.ReadAsInt32()); break;
case "uint": args.Add((uint?)reader.ReadAsDecimal()); break;
case "ulong": args.Add((ulong?)reader.ReadAsDecimal()); break;
default:
var jtr = (JsonTextReader)reader;
throw new JsonSerializationException($"Unsupported {nameof(CompositeKey)} type '{name}'.", jtr.Path, jtr.LineNumber, jtr.LinePosition, null);
}

reader.Read();
if (reader.TokenType != JsonToken.EndObject)
{
var jtr = (JsonTextReader)reader;
throw new JsonSerializationException($"Expected {nameof(JsonToken.EndObject)} for a {nameof(CompositeKey)} argument; found {reader.TokenType}.", jtr.Path, jtr.LineNumber, jtr.LinePosition, null);
}
}

reader.Read();
}

return new CompositeKey([.. args]);
}

/// <inheritdoc/>
public override void WriteJson(JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer)
{
if (value is not CompositeKey key || key.Args.Length == 0)
{
writer.WriteNull();
return;
}

writer.WriteStartArray();

foreach (var arg in key.Args)
{
if (arg is null)
{
writer.WriteNull();
continue;
}

writer.WriteStartObject();

_ = arg switch
{
string str => JsonWrite(writer, "string", () => writer.WriteValue(str)),
char c => JsonWrite(writer, "char", () => writer.WriteValue(c.ToString())),
short s => JsonWrite(writer, "short", () => writer.WriteValue(s)),
int i => JsonWrite(writer, "int", () => writer.WriteValue(i)),
long l => JsonWrite(writer, "long", () => writer.WriteValue(l)),
Guid g => JsonWrite(writer, "guid", () => writer.WriteValue(g)),
DateTime d => JsonWrite(writer, "datetime", () => writer.WriteValue(d)),
DateTimeOffset o => JsonWrite(writer, "datetimeoffset", () => writer.WriteValue(o)),
ushort us => JsonWrite(writer, "ushort", () => writer.WriteValue(us)),
uint ui => JsonWrite(writer, "uint", () => writer.WriteValue(ui)),
ulong ul => JsonWrite(writer, "ulong", () => writer.WriteValue(ul)),
_ => throw new JsonException($"Unsupported {nameof(CompositeKey)} type '{arg.GetType().Name}'.")
};

writer.WriteEndObject();
}

writer.WriteEndArray();
}

/// <summary>
/// Provides a simple means to write a JSON property name and value.
/// </summary>
private static bool JsonWrite(JsonWriter writer, string name, Action action)
{
writer.WritePropertyName(name);
action();
return true;
}
}
}
6 changes: 3 additions & 3 deletions src/CoreEx.Newtonsoft/Json/JsonSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx

using CoreEx.Json;
using CoreEx.RefData;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
Expand All @@ -28,7 +27,8 @@ public class JsonSerializer : IJsonSerializer
/// <item><description><see cref="JsonSerializerSettings.NullValueHandling"/> = <see cref="NullValueHandling.Ignore"/>.</description></item>
/// <item><description><see cref="JsonSerializerSettings.Formatting"/> = <see cref="Formatting.None"/>.</description></item>
/// <item><description><see cref="JsonSerializerSettings.ContractResolver"/> = <see cref="ContractResolver.Default"/>.</description></item>
/// <item><description><see cref="JsonSerializerSettings.Converters"/> = <see cref="Nsj.Converters.StringEnumConverter"/>, <see cref="ReferenceDataJsonConverter"/>, <see cref="CollectionResultJsonConverter"/>.</description></item>
/// <item><description><see cref="JsonSerializerSettings.Converters"/> = <see cref="Nsj.Converters.StringEnumConverter"/>, <see cref="ReferenceDataJsonConverter"/>, <see cref="CollectionResultJsonConverter"/>
/// and <see cref="CompositeKeyJsonConverter"/>.</description></item>
/// </list>
/// </remarks>
public static JsonSerializerSettings DefaultSettings { get; set; } = new JsonSerializerSettings
Expand All @@ -37,7 +37,7 @@ public class JsonSerializer : IJsonSerializer
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.None,
ContractResolver = ContractResolver.Default,
Converters = { new Nsj.Converters.StringEnumConverter(), new ReferenceDataJsonConverter(), new CollectionResultJsonConverter() }
Converters = { new Nsj.Converters.StringEnumConverter(), new ReferenceDataJsonConverter(), new CollectionResultJsonConverter(), new CompositeKeyJsonConverter() }
};

/// <summary>
Expand Down
Loading

0 comments on commit 8f0d72d

Please sign in to comment.