Skip to content

Commit cf3b084

Browse files
committed
[Host.Serialization.SystemTextJson] Pass the actual message type by default during serialization
Signed-off-by: Tomasz Maruszak <maruszaktomasz@gmail.com>
1 parent 547a250 commit cf3b084

File tree

5 files changed

+79
-13
lines changed

5 files changed

+79
-13
lines changed

docs/serialization.md

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Please read the [Introduction](intro.md) before reading this provider documentat
44

55
- [Configuration](#configuration)
66
- [Json (System.Text.Json)](#json-systemtextjson)
7+
- [Polymorphic Type Handling](#polymorphic-type-handling)
8+
- [Object to Inferred Types Converter](#object-to-inferred-types-converter)
79
- [Json (Newtonsoft.Json)](#json-newtonsoftjson)
810
- [Avro](#avro)
911
- [GoogleProtobuf](#googleprotobuf)
@@ -83,7 +85,64 @@ services.AddSlimMessageBus(mbb =>
8385
});
8486
```
8587

86-
By default the plugin adds a custom converter (see [`ObjectToInferredTypesConverter`](../src/SlimMessageBus.Host.Serialization.SystemTextJson/ObjectToInferredTypesConverter.cs)) that infers primitive types whenever the type to deseriaize is object (unknown). This helps with header value serialization for transport providers that transmit the headers as binary (Kafka). See the source code for better explanation.
88+
### Polymorphic Type Handling
89+
90+
By default, the serializer uses the `useActualTypeOnSerialize: true` parameter, which ensures that the actual runtime type of the message is used during serialization. This is important for polymorphic message types where you might publish a derived type but declare the producer with a base type.
91+
92+
```cs
93+
// Default behavior - uses actual type during serialization
94+
mbb.AddJsonSerializer(useActualTypeOnSerialize: true);
95+
96+
// Alternative - uses declared type during serialization
97+
mbb.AddJsonSerializer(useActualTypeOnSerialize: false);
98+
```
99+
100+
**When `useActualTypeOnSerialize: true` (default):**
101+
102+
- The serializer uses `message.GetType()` to determine which properties to serialize
103+
- All properties of the derived type are included in the JSON
104+
- Useful when consumers know the actual message type and need all properties
105+
- Works without requiring `[JsonDerivedType]` attributes on base message types
106+
107+
**When `useActualTypeOnSerialize: false`:**
108+
109+
- The serializer uses the declared `messageType` parameter passed to the serialize method
110+
- Only properties of the declared base type are serialized
111+
- Useful for strict contract enforcement where consumers should only see base type properties
112+
- Requires `[JsonDerivedType]` attributes on base types if you need polymorphic deserialization
113+
114+
**Example:**
115+
116+
```cs
117+
public class BaseMessage
118+
{
119+
public string Id { get; set; }
120+
}
121+
122+
public class DerivedMessage : BaseMessage
123+
{
124+
public string ExtraData { get; set; }
125+
}
126+
127+
// Producer configuration
128+
mbb.Produce<BaseMessage>(x => x.DefaultTopic("my-topic"));
129+
130+
// When publishing
131+
var message = new DerivedMessage { Id = "123", ExtraData = "test" };
132+
await bus.Publish(message);
133+
134+
// With useActualTypeOnSerialize: true (default)
135+
// Serialized JSON: {"id":"123","extraData":"test"}
136+
137+
// With useActualTypeOnSerialize: false
138+
// Serialized JSON: {"id":"123"}
139+
```
140+
141+
> **Note:** System.Text.Json requires `[JsonDerivedType]` attributes on the base type for proper polymorphic deserialization. The `useActualTypeOnSerialize: true` setting helps with serialization but doesn't affect deserialization requirements.
142+
143+
### Object to Inferred Types Converter
144+
145+
By default the plugin adds a custom converter (see [`ObjectToInferredTypesConverter`](../src/SlimMessageBus.Host.Serialization.SystemTextJson/ObjectToInferredTypesConverter.cs)) that infers primitive types whenever the type to deserialize is object (unknown). This helps with header value serialization for transport providers that transmit the headers as binary (Kafka). See the source code for better explanation.
87146

88147
## Json (Newtonsoft.Json)
89148

@@ -110,6 +169,8 @@ var jsonSerializerSettings = new Newtonsoft.Json.JsonSerializerSettings
110169
mbb.AddJsonSerializer(jsonSerializerSettings, Encoding.UTF8);
111170
```
112171

172+
> **Note:** Newtonsoft.Json always serializes using the declared type (`messageType` parameter). For polymorphic type handling with Newtonsoft.Json, use the `TypeNameHandling` setting in `JsonSerializerSettings`.
173+
113174
## Avro
114175

115176
Nuget package: [SlimMessageBus.Host.Serialization.Avro](https://www.nuget.org/packages/SlimMessageBus.Host.Serialization.Avro)

src/Host.Plugin.Properties.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<Import Project="Common.NuGet.Properties.xml" />
55

66
<PropertyGroup>
7-
<Version>3.3.7-rc100</Version>
7+
<Version>3.4.0-rc100</Version>
88
</PropertyGroup>
99

1010
</Project>

src/SlimMessageBus.Host.Serialization.SystemTextJson/JsonMessageSerializer.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
namespace SlimMessageBus.Host.Serialization.SystemTextJson;
22

3-
using System.Collections.Generic;
4-
using System.Text.Json;
5-
using System.Text.Json.Serialization;
3+
using System.Collections.Generic;
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
66

77
/// <summary>
88
/// Implementation of <see cref="IMessageSerializer"/> using <see cref="JsonSerializer"/>.
99
/// </summary>
10-
public class JsonMessageSerializer : IMessageSerializer, IMessageSerializer<string>, IMessageSerializerProvider
11-
{
10+
public class JsonMessageSerializer : IMessageSerializer, IMessageSerializer<string>, IMessageSerializerProvider
11+
{
12+
private readonly bool _useActualTypeOnSerialize;
13+
1214
/// <summary>
1315
/// <see cref="JsonSerializerOptions"/> options for the JSON serializer. By default adds <see cref="ObjectToInferredTypesConverter"/> converter.
1416
/// </summary>
1517
public JsonSerializerOptions Options { get; set; }
1618

17-
public JsonMessageSerializer(JsonSerializerOptions options = null)
19+
public JsonMessageSerializer(JsonSerializerOptions options = null, bool useActualTypeOnSerialize = true)
1820
{
1921
Options = options ?? CreateDefaultOptions();
22+
_useActualTypeOnSerialize = useActualTypeOnSerialize;
2023
}
2124

2225
public virtual JsonSerializerOptions CreateDefaultOptions()
@@ -34,7 +37,7 @@ public virtual JsonSerializerOptions CreateDefaultOptions()
3437
#region Implementation of IMessageSerializer
3538

3639
public byte[] Serialize(Type messageType, IDictionary<string, object> headers, object message, object transportMessage)
37-
=> JsonSerializer.SerializeToUtf8Bytes(message, messageType, Options);
40+
=> JsonSerializer.SerializeToUtf8Bytes(message, _useActualTypeOnSerialize && message != null ? message.GetType() : messageType, Options);
3841

3942
public object Deserialize(Type messageType, IReadOnlyDictionary<string, object> headers, byte[] payload, object transportMessage)
4043
=> JsonSerializer.Deserialize(payload, messageType, Options)!;
@@ -44,7 +47,7 @@ public object Deserialize(Type messageType, IReadOnlyDictionary<string, object>
4447
#region Implementation of IMessageSerializer<string>
4548

4649
string IMessageSerializer<string>.Serialize(Type messageType, IDictionary<string, object> headers, object message, object transportMessage)
47-
=> JsonSerializer.Serialize(message, messageType, Options);
50+
=> JsonSerializer.Serialize(message, _useActualTypeOnSerialize && message != null ? message.GetType() : messageType, Options);
4851

4952
public object Deserialize(Type messageType, IReadOnlyDictionary<string, object> headers, string payload, object transportMessage)
5053
=> JsonSerializer.Deserialize(payload, messageType, Options)!;

src/SlimMessageBus.Host.Serialization.SystemTextJson/SerializationBuilderExtensions.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ public static class SerializationBuilderExtensions
1313
/// Registers the <see cref="IMessageSerializer"/> with implementation as <see cref="JsonMessageSerializer"/>.
1414
/// </summary>
1515
/// <param name="builder"></param>
16+
/// <param name="options"></param>
17+
/// <param name="useActualTypeOnSerialize">Should the actual message type be used during serialization? If false, the producer declared type will be passed (e.g. base type of the actual message). It will make a difference for polymorphic types.</param>
1618
/// <returns></returns>
17-
public static TBuilder AddJsonSerializer<TBuilder>(this TBuilder builder, JsonSerializerOptions options = null)
19+
public static TBuilder AddJsonSerializer<TBuilder>(this TBuilder builder, JsonSerializerOptions options = null, bool useActualTypeOnSerialize = true)
1820
where TBuilder : ISerializationBuilder
1921
{
2022
builder.RegisterSerializer<JsonMessageSerializer>(services =>
2123
{
2224
// Add the implementation
23-
services.TryAddSingleton(svp => new JsonMessageSerializer(options ?? svp.GetService<JsonSerializerOptions>()));
25+
services.TryAddSingleton(svp => new JsonMessageSerializer(options ?? svp.GetService<JsonSerializerOptions>(), useActualTypeOnSerialize));
2426
// Add the serializer as IMessageSerializer<string>
2527
services.TryAddSingleton(svp => svp.GetRequiredService<JsonMessageSerializer>() as IMessageSerializer<string>);
2628
});

src/Tests/SlimMessageBus.Host.Serialization.SystemTextJson.Test/JsonMessageSerializerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void When_SerializeAndDeserialize_Given_TypeObjectAndBytesPayload_Then_Tr
2020
// arrange
2121
var subject = new JsonMessageSerializer();
2222

23-
// actx
23+
// act
2424
var bytes = subject.Serialize(typeof(object), null, value, null);
2525
var deserializedValue = subject.Deserialize(typeof(object), null, bytes, null);
2626

0 commit comments

Comments
 (0)