mirror of https://github.com/dapr/dotnet-sdk.git
Make ActorId deserializable (#457)
Fixes: #444 ActorId doesn't define a default constructor, and so it's not deserializable via System.Text.Json. This change implements a converter so that ActorId's API shape can work with the serializer properly. I added an integration test for ActorReference as well, but no library changes we needed for that, ActorId was the blocker.
This commit is contained in:
parent
29d0f7c38c
commit
7d1fa13101
|
|
@ -7,10 +7,13 @@ namespace Dapr.Actors
|
|||
{
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
using Dapr.Actors.Seralization;
|
||||
|
||||
/// <summary>
|
||||
/// The ActorId represents the identity of an actor within an actor service.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(ActorIdJsonConverter))]
|
||||
[DataContract(Name = "ActorId")]
|
||||
public class ActorId
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
namespace Dapr.Actors.Seralization
|
||||
{
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
// Converter for ActorId - will be serialized as a JSON string
|
||||
internal class ActorIdJsonConverter : JsonConverter<ActorId>
|
||||
{
|
||||
public override ActorId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (typeToConvert != typeof(ActorId))
|
||||
{
|
||||
throw new ArgumentException( $"Conversion to the type '{typeToConvert}' is not supported.", nameof(typeToConvert));
|
||||
}
|
||||
|
||||
// Note - we generate random Guids for Actor Ids when we're generating them randomly
|
||||
// but we don't actually enforce a format. Ids could be a number, or a date, or whatever,
|
||||
// we don't really care. However we always **represent** Ids in JSON as strings.
|
||||
if (reader.TokenType == JsonTokenType.String &&
|
||||
reader.GetString() is string text &&
|
||||
!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return new ActorId(text);
|
||||
}
|
||||
else if (reader.TokenType == JsonTokenType.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new JsonException(); // The serializer will provide a default error message.
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, ActorId value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
writer.WriteNullValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStringValue(value.GetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// ------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
// ------------------------------------------------------------
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Dapr.Actors.Serialization
|
||||
{
|
||||
public class ActorIdJsonConverterTest
|
||||
{
|
||||
[Fact]
|
||||
public void CanSerializeActorId()
|
||||
{
|
||||
var id = ActorId.CreateRandom();
|
||||
var document = new { actor = id, };
|
||||
|
||||
// We use strings for ActorId - the result should be the same as passing the Id directly.
|
||||
var expected = JsonSerializer.Serialize(new { actor = id.GetId(), });
|
||||
|
||||
var serialized = JsonSerializer.Serialize(document);
|
||||
|
||||
Assert.Equal(expected, serialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSerializeNullActorId()
|
||||
{
|
||||
var document = new { actor = (ActorId)null, };
|
||||
|
||||
var expected = JsonSerializer.Serialize(new { actor = (string)null, });
|
||||
|
||||
var serialized = JsonSerializer.Serialize(document);
|
||||
|
||||
Assert.Equal(expected, serialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeserializeActorId()
|
||||
{
|
||||
var id = ActorId.CreateRandom().GetId();
|
||||
var document = $@"
|
||||
{{
|
||||
""actor"": ""{id}""
|
||||
}}";
|
||||
|
||||
var deserialized = JsonSerializer.Deserialize<ActorHolder>(document);
|
||||
|
||||
Assert.Equal(id, deserialized.Actor.GetId());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDeserializeNullActorId()
|
||||
{
|
||||
var id = ActorId.CreateRandom().GetId();
|
||||
var document = $@"
|
||||
{{
|
||||
""actor"": null
|
||||
}}";
|
||||
|
||||
var deserialized = JsonSerializer.Deserialize<ActorHolder>(document);
|
||||
|
||||
Assert.Null(deserialized.Actor);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{ \"actor\": ")]
|
||||
[InlineData("{ \"actor\": \"hi")]
|
||||
[InlineData("{ \"actor\": }")]
|
||||
[InlineData("{ \"actor\": 3 }")]
|
||||
[InlineData("{ \"actor\": \"\"}")]
|
||||
[InlineData("{ \"actor\": \" \"}")]
|
||||
public void CanReportErrorsFromInvalidData(string document)
|
||||
{
|
||||
// The error messages are provided by the serializer, don't test them here
|
||||
// that would be fragile.
|
||||
Assert.Throws<JsonException>(() =>
|
||||
{
|
||||
JsonSerializer.Deserialize<ActorHolder>(document);
|
||||
});
|
||||
}
|
||||
|
||||
// Regression test for #444
|
||||
[Fact]
|
||||
public void CanRoundTripActorReference()
|
||||
{
|
||||
var reference = new ActorReference()
|
||||
{
|
||||
ActorId = ActorId.CreateRandom(),
|
||||
ActorType = "TestActor",
|
||||
};
|
||||
|
||||
var serialized = JsonSerializer.Serialize(reference);
|
||||
var deserialized = JsonSerializer.Deserialize<ActorReference>(serialized);
|
||||
|
||||
Assert.Equal(reference.ActorId.GetId(), deserialized.ActorId.GetId());
|
||||
Assert.Equal(reference.ActorType, deserialized.ActorType);
|
||||
}
|
||||
|
||||
private class ActorHolder
|
||||
{
|
||||
[JsonPropertyName("actor")]
|
||||
public ActorId Actor { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue