Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Setup class for NewtonSoftJsonSerializer #4890

Merged
13 changes: 13 additions & 0 deletions docs/articles/networking/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,19 @@ The only thing left to do for this class would be to fill in the serialization l
Afterwards the configuration would need to be updated to reflect which name to bind to and the classes that use this
serializer.

### Programatically change NewtonSoft JSON serializer settings
You can change the JSON serializer behaviour by using the `NewtonSoftJsonSerializerSetup` class to programatically
change the settings used inside the Json serializer by passing it into the an `ActorSystemSetup`.

[!code-csharp[Main](../../../src/core/Akka.Docs.Tests/Networking/Serialization/ProgrammaticJsonSerializerSetup.cs?name=CustomJsonSetup)]

Note that, while we try to keep everything to be compatible, there are no guarantee that your specific serializer settings use case is compatible with the rest of
Akka.NET serialization schemes; please test your system in a development environment before deploying it into production.

There are a couple limitation with this method, in that you can not change the `ObjectCreationHandling` and the `ContractResolver` settings
in the Json settings object. Those settings, by default, will always be overriden with `ObjectCreationHandling.Replace` and the [`AkkaContractResolver`](xref:Akka.Serialization.NewtonSoftJsonSerializer.AkkaContractResolver)
object respectively.

### Serializer with String Manifest
The `Serializer` illustrated above supports a class-based manifest (type hint).
For serialization of data that need to evolve over time, the [`SerializerWithStringManifest`](xref:Akka.Serialization.SerializerWithStringManifest) is recommended instead of `Serializer` because the manifest (type hint) is a `String` instead of a `Type`.
Expand Down
5 changes: 5 additions & 0 deletions src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4654,6 +4654,11 @@ namespace Akka.Serialization
public bool PreserveObjectReferences { get; }
public static Akka.Serialization.NewtonSoftJsonSerializerSettings Create(Akka.Configuration.Config config) { }
}
public sealed class NewtonSoftJsonSerializerSetup : Akka.Actor.Setup.Setup
{
public System.Action<Newtonsoft.Json.JsonSerializerSettings> ApplySettings { get; }
public static Akka.Serialization.NewtonSoftJsonSerializerSetup Create(System.Action<Newtonsoft.Json.JsonSerializerSettings> settings) { }
}
public class NullSerializer : Akka.Serialization.Serializer
{
public NullSerializer(Akka.Actor.ExtendedActorSystem system) { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Actor.Setup;
using Akka.Serialization;
using Newtonsoft.Json;

namespace DocsExamples.Networking.Serialization
{
public class ProgrammaticJsonSerializerSetup
{
public ProgrammaticJsonSerializerSetup()
{
#region CustomJsonSetup
var jsonSerializerSetup = NewtonSoftJsonSerializerSetup.Create(
settings =>
{
settings.NullValueHandling = NullValueHandling.Include;
settings.PreserveReferencesHandling = PreserveReferencesHandling.None;
settings.Formatting = Formatting.None;
});

var systemSetup = ActorSystemSetup.Create(jsonSerializerSetup);

var system = ActorSystem.Create("MySystem", systemSetup);
#endregion

system.Terminate().Wait();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Actor.Setup;
using Akka.Configuration;
using Akka.Serialization;
using Akka.TestKit;
using Akka.TestKit.Configs;
using FluentAssertions;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using Xunit;
using Xunit.Abstractions;

namespace Akka.Tests.Serialization
{
public class NewtonSoftJsonSerializerSetupSpec : AkkaSpec
{
internal class DummyContractResolver : DefaultContractResolver
{ }

public static NewtonSoftJsonSerializerSetup SerializationSettings = NewtonSoftJsonSerializerSetup.Create(
settings =>
{
settings.ReferenceLoopHandling = ReferenceLoopHandling.Error;
settings.MissingMemberHandling = MissingMemberHandling.Error;
settings.NullValueHandling = NullValueHandling.Include;
settings.Converters = new List<JsonConverter> { new DummyConverter() };
settings.ObjectCreationHandling = ObjectCreationHandling.Auto;
settings.ContractResolver = new DummyContractResolver();
});

public static readonly BootstrapSetup Bootstrap = BootstrapSetup.Create().WithConfig(TestConfigs.DefaultConfig);

public static readonly ActorSystemSetup ActorSystemSettings = ActorSystemSetup.Create(SerializationSettings, Bootstrap);

public NewtonSoftJsonSerializerSetupSpec(ITestOutputHelper output)
: base(ActorSystem.Create("SerializationSettingsSpec", ActorSystemSettings), output) { }


[Fact]
public void Setup_should_be_used_inside_Json_serializer()
{
var serializer = (NewtonSoftJsonSerializer) Sys.Serialization.FindSerializerForType(typeof(object));
var settings = serializer.Settings;
settings.ReferenceLoopHandling.Should().Be(ReferenceLoopHandling.Error);
settings.MissingMemberHandling.Should().Be(MissingMemberHandling.Error);
settings.NullValueHandling.Should().Be(NullValueHandling.Include);
settings.Converters.Any(c => c is DummyConverter).Should().Be(true);
}

[Fact]
public void Setup_should_not_change_mandatory_settings()
{
var serializer = (NewtonSoftJsonSerializer) Sys.Serialization.FindSerializerForType(typeof(object));
var settings = serializer.Settings;
settings.ContractResolver.Should().BeOfType<NewtonSoftJsonSerializer.AkkaContractResolver>();
settings.ObjectCreationHandling.Should().Be(ObjectCreationHandling.Replace);
settings.Converters.Any(c => c is NewtonSoftJsonSerializer.SurrogateConverter).Should().Be(true);
settings.Converters.Any(c => c is DiscriminatedUnionConverter).Should().Be(true);
}
}
}
39 changes: 26 additions & 13 deletions src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,30 +143,43 @@ public NewtonSoftJsonSerializer(ExtendedActorSystem system, Config config)
public NewtonSoftJsonSerializer(ExtendedActorSystem system, NewtonSoftJsonSerializerSettings settings)
: base(system)
{
var converters = settings.Converters
.Select(type => CreateConverter(type, system))
.ToList();

converters.Add(new SurrogateConverter(this));
converters.Add(new DiscriminatedUnionConverter());

Settings = new JsonSerializerSettings
{
PreserveReferencesHandling = settings.PreserveObjectReferences
? PreserveReferencesHandling.Objects
PreserveReferencesHandling = settings.PreserveObjectReferences
? PreserveReferencesHandling.Objects
: PreserveReferencesHandling.None,
Converters = converters,
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
ObjectCreationHandling = ObjectCreationHandling.Replace, //important: if reuse, the serializer will overwrite properties in default references, e.g. Props.DefaultDeploy or Props.noArgs
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
TypeNameHandling = settings.EncodeTypeNames
? TypeNameHandling.All
? TypeNameHandling.All
: TypeNameHandling.None,
ContractResolver = new AkkaContractResolver()
};

if (system != null)
{
var settingsSetup = system.Settings.Setup.Get<NewtonSoftJsonSerializerSetup>()
.GetOrElse(NewtonSoftJsonSerializerSetup.Create(s => {}));

settingsSetup.ApplySettings(Settings);
}

var converters = settings.Converters
.Select(type => CreateConverter(type, system))
.ToList();

converters.Add(new SurrogateConverter(this));
converters.Add(new DiscriminatedUnionConverter());

foreach (var converter in converters)
{
Settings.Converters.Add(converter);
}

Settings.ObjectCreationHandling = ObjectCreationHandling.Replace; //important: if reuse, the serializer will overwrite properties in default references, e.g. Props.DefaultDeploy or Props.noArgs
Settings.ContractResolver = new AkkaContractResolver();

_serializer = JsonSerializer.Create(Settings);
}

Expand Down
32 changes: 32 additions & 0 deletions src/core/Akka/Serialization/NewtonSoftJsonSerializerSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Text;
using Akka.Actor.Setup;
using Newtonsoft.Json;

namespace Akka.Serialization
{
/// <summary>
/// Setup for the <see cref="NewtonSoftJsonSerializer"/> serializer.
///
/// Constructor is INTERNAL API. Use the factory method <see cref="Create"/>.
///
/// NOTE:
/// - <see cref="JsonSerializerSettings.ObjectCreationHandling"/> will always be overriden with
/// <see cref="ObjectCreationHandling.Replace"/>
/// - <see cref="JsonSerializerSettings.ContractResolver"/> will always be overriden with the internal
/// contract resolver <see cref="NewtonSoftJsonSerializer.AkkaContractResolver"/>
/// </summary>
public sealed class NewtonSoftJsonSerializerSetup : Setup
{
public static NewtonSoftJsonSerializerSetup Create(Action<JsonSerializerSettings> settings)
=> new NewtonSoftJsonSerializerSetup(settings);

public Action<JsonSerializerSettings> ApplySettings { get; }

private NewtonSoftJsonSerializerSetup(Action<JsonSerializerSettings> settings)
{
ApplySettings = settings;
}
}
}