Overview
Savvyio.Extensions.Newtonsoft.Json adapts Savvy I/O contracts to Newtonsoft.Json. It extends Savvyio.Domain and Savvyio.Messaging with a marshaller and converters that round-trip IRequest, IMetadataDictionary, IMessage<T>, signed messages, cloud events, and SingleValueObject<T> instances through Json.NET.
Through NewtonsoftJsonMarshaller, the package installs default converters for requests, metadata dictionaries, messages, and single-value objects. More specialized domain models, including ValueObject and AggregateRoot<TKey>, are available through explicit converters when you need constructor-based rehydration or fallback to a writable default constructor.
Key APIs
NewtonsoftJsonMarshaller is the package's main entry point for stream-based serialization. It exposes generic and type-based Serialize and Deserialize overloads, a Create factory, and a Default instance that uses Formatting.None; its static initialization path also wires the default Savvy I/O converters into NewtonsoftJsonFormatterOptions.
JsonConverterExtensions.AddRequestConverter registers RequestConverter, which is deserialization-only. The converter creates an uninitialized IRequest implementation and then sets writable properties or matching backing fields, so request types must use writable members, auto-properties, or a backing-field naming pattern the converter can resolve.
JsonConverterExtensions.AddMessageConverter registers MessageConverter for IMessage<T> shapes. Source and tests show it preserves the message envelope, reconstructs the concrete Data type from metadata, handles ISignedMessage<T> signatures, supports CloudEvents specversion, and carries over dictionary-backed extension attributes.
JsonConverterExtensions.AddMetadataDictionaryConverter adds IMetadataDictionary support to a converter collection. The registered converter materializes a MetadataDictionary from JSON object properties and is used both directly in tests and indirectly when message payload metadata is deserialized.
JsonConverterExtensions.AddSingleValueObjectConverter adds scalar handling for SingleValueObject<T>. The converter writes only the wrapped Value and recreates the domain type by invoking its single-argument constructor during deserialization.
JsonConverterExtensions.AddValueObjectConverter adds the package's ValueObjectConverter for full ValueObject hierarchies. It serializes readable properties, flattens nested single-value objects to scalars, and rehydrates by matching constructor parameter types or, when available, by populating a writable default-constructed instance; otherwise it throws InvalidOperationException.
JsonConverterExtensions.AddAggregateRootConverter<TKey> adds AggregateRootConverter<TKey> for explicit aggregate root serialization. The converter writes Id plus readable runtime properties, supports single-value object properties during rehydration, and uses the same matching-constructor or writable default-constructor fallback pattern as the value object converter.
JsonSerializerExtensions.ResolvePropertyKeyByConvention and ResolveDictionaryKeyByConvention align the converters with the active Json.NET naming strategy. Tests show property keys follow camelCase when a CamelCaseNamingStrategy is configured, while dictionary keys continue to use Json.NET's dictionary-key resolution behavior.
Basic usage
using Codebelt.Extensions.Xunit;
using Newtonsoft.Json;
using Savvyio.Domain;
using Savvyio.Extensions.Newtonsoft.Json;
using Xunit;
namespace MyProject.Tests;
public class ValueObjectConverterTests : Test
{
public ValueObjectConverterTests(ITestOutputHelper output) : base(output) { }
[Fact]
public void AddValueObjectConverter_NestedValueObject_RoundTripsJson()
{
var settings = new JsonSerializerSettings();
settings.Converters
.AddValueObjectConverter()
.AddSingleValueObjectConverter();
var original = new MemberProfile("Jane Doe", new MemberAlias("jdoe"));
var json = JsonConvert.SerializeObject(original, settings);
var rehydrated = Assert.IsType<MemberProfile>(
JsonConvert.DeserializeObject<MemberProfile>(json, settings));
TestOutput.WriteLine(json);
Assert.Equal("Jane Doe", rehydrated.Name);
Assert.Equal("jdoe", rehydrated.Alias.Value);
}
private sealed record MemberAlias : SingleValueObject<string>
{
public MemberAlias(string value) : base(value) { }
}
private sealed record MemberProfile : ValueObject
{
public MemberProfile(string name, MemberAlias alias)
{
Name = name;
Alias = alias;
}
public string Name { get; }
public MemberAlias Alias { get; }
}
}
Use this pattern when your domain model includes ValueObject types and nested SingleValueObject<T> members that need explicit Newtonsoft.Json converter registration.
It matters because this package can reconstruct those domain types from their public shape instead of forcing DTO-only serialization around your model.
Installation
dotnet add package Savvyio.Extensions.Newtonsoft.Json
Usage guidance
Use this package when a Newtonsoft.Json boundary needs to carry Savvy I/O requests, messages, metadata dictionaries, or domain value types without hand-written converters for each contract. If you only serialize plain CLR types, or you want container registration instead of direct converter and marshaller usage, prefer plain Newtonsoft.Json or Savvyio.Extensions.DependencyInjection.Newtonsoft.Json.
Family packages
- 🏭Savvyio.App
- 📦Savvyio.Commands
- 📦Savvyio.Commands.Messaging
- 📦Savvyio.Core
- 📦Savvyio.Domain
- 📦Savvyio.Domain.EventSourcing
- 📦Savvyio.EventDriven
- 📦Savvyio.EventDriven.Messaging
- 📦Savvyio.Extensions.Dapper
- 📦Savvyio.Extensions.DapperExtensions
- 📦Savvyio.Extensions.DependencyInjection
- 📦Savvyio.Extensions.DependencyInjection.Dapper
- 📦Savvyio.Extensions.DependencyInjection.DapperExtensions
- 📦Savvyio.Extensions.DependencyInjection.Domain
- 📦Savvyio.Extensions.DependencyInjection.EFCore
- 📦Savvyio.Extensions.DependencyInjection.EFCore.Domain
- 📦Savvyio.Extensions.DependencyInjection.EFCore.Domain.EventSourcing
- 📦Savvyio.Extensions.DependencyInjection.NATS
- 📝Savvyio.Extensions.DependencyInjection.Newtonsoft.Json
- 📦Savvyio.Extensions.DependencyInjection.QueueStorage
- 📦Savvyio.Extensions.DependencyInjection.RabbitMQ
- 📦Savvyio.Extensions.DependencyInjection.SimpleQueueService
- 📝Savvyio.Extensions.DependencyInjection.Text.Json
- 📦Savvyio.Extensions.Dispatchers
- 📦Savvyio.Extensions.EFCore
- 📦Savvyio.Extensions.EFCore.Domain
- 📦Savvyio.Extensions.EFCore.Domain.EventSourcing
- 📦Savvyio.Extensions.NATS
- 📦Savvyio.Extensions.QueueStorage
- 📦Savvyio.Extensions.RabbitMQ
- 📦Savvyio.Extensions.SimpleQueueService
- 📝Savvyio.Extensions.Text.Json
- 📦Savvyio.Messaging
- 📦Savvyio.Queries