diff --git a/docs/articles/networking/serialization.md b/docs/articles/networking/serialization.md index 6ea60eea99c..300fa1bba3e 100644 --- a/docs/articles/networking/serialization.md +++ b/docs/articles/networking/serialization.md @@ -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`. diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt index f0e808d26ce..7e964110a5b 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt @@ -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 ApplySettings { get; } + public static Akka.Serialization.NewtonSoftJsonSerializerSetup Create(System.Action settings) { } + } public class NullSerializer : Akka.Serialization.Serializer { public NullSerializer(Akka.Actor.ExtendedActorSystem system) { } diff --git a/src/core/Akka.Docs.Tests/Networking/Serialization/ProgrammaticJsonSerializerSetup.cs b/src/core/Akka.Docs.Tests/Networking/Serialization/ProgrammaticJsonSerializerSetup.cs new file mode 100644 index 00000000000..4e14f57c49d --- /dev/null +++ b/src/core/Akka.Docs.Tests/Networking/Serialization/ProgrammaticJsonSerializerSetup.cs @@ -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(); + } + } +} diff --git a/src/core/Akka.Tests/Serialization/NewtonSoftJsonSerializerSetupSpec.cs b/src/core/Akka.Tests/Serialization/NewtonSoftJsonSerializerSetupSpec.cs new file mode 100644 index 00000000000..4529b196f9b --- /dev/null +++ b/src/core/Akka.Tests/Serialization/NewtonSoftJsonSerializerSetupSpec.cs @@ -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 { 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(); + 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); + } + } +} diff --git a/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs b/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs index 146e5136dcc..274c873a4c5 100644 --- a/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs +++ b/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs @@ -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() + .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); } diff --git a/src/core/Akka/Serialization/NewtonSoftJsonSerializerSetup.cs b/src/core/Akka/Serialization/NewtonSoftJsonSerializerSetup.cs new file mode 100644 index 00000000000..662332d2026 --- /dev/null +++ b/src/core/Akka/Serialization/NewtonSoftJsonSerializerSetup.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Akka.Actor.Setup; +using Newtonsoft.Json; + +namespace Akka.Serialization +{ + /// + /// Setup for the serializer. + /// + /// Constructor is INTERNAL API. Use the factory method . + /// + /// NOTE: + /// - will always be overriden with + /// + /// - will always be overriden with the internal + /// contract resolver + /// + public sealed class NewtonSoftJsonSerializerSetup : Setup + { + public static NewtonSoftJsonSerializerSetup Create(Action settings) + => new NewtonSoftJsonSerializerSetup(settings); + + public Action ApplySettings { get; } + + private NewtonSoftJsonSerializerSetup(Action settings) + { + ApplySettings = settings; + } + } +}