Codebelt

Savvyio.Extensions.Newtonsoft.Json

Use Newtonsoft.Json with Savvy I/O message contracts, metadata dictionaries, and opt-in domain converters.

.NET 10.0 / .NET 9.0 MIT v5.0.8 10,245 downloads

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