You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用单个JsonConverter序列化ValueObject<T>子类时省略Value属性

Generic JsonConverter for ValueObject Subclasses (Direct Value Serialization)

Great approach so far! You’re on exactly the right track with a single generic converter that handles all ValueObject<T> subclasses—no need to duplicate code for every subclass like CustomerId or EmailAddress. Let’s fill in the missing WriteJson method and polish the implementation to get the clean serialization output you want.

Complete ValueObjectOfTConverter Implementation

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;

public class ValueObjectOfTConverter : JsonConverter
{
    private static readonly Type _valueObjectGenericType = typeof(ValueObject<>);
    private static readonly string _valuePropertyName = nameof(ValueObject<object>.Value);

    public override bool CanConvert(Type objectType) 
        => IsSubclassOfGenericType(objectType, _valueObjectGenericType);

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Wrap the raw value in a JObject with the "Value" property to match the ValueObject<T> structure
        var rawValueToken = JToken.Load(reader);
        var wrappedObject = new JObject(new JProperty(_valuePropertyName, rawValueToken));
        return wrappedObject.ToObject(objectType, serializer);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // Get the underlying value from the ValueObject<T> instance
        PropertyInfo valueProperty = value.GetType().GetProperty(_valuePropertyName);
        object underlyingValue = valueProperty.GetValue(value);

        // Let the serializer handle the raw value directly (preserves default behavior like Guid string conversion)
        serializer.Serialize(writer, underlyingValue);
    }

    private static bool IsSubclassOfGenericType(Type typeToCheck, Type openGenericType)
    {
        while (typeToCheck != null && typeToCheck != typeof(object))
        {
            Type currentTypeDefinition = typeToCheck.IsGenericType 
                ? typeToCheck.GetGenericTypeDefinition() 
                : typeToCheck;
            
            if (openGenericType == currentTypeDefinition)
                return true;
            
            typeToCheck = typeToCheck.BaseType;
        }
        return false;
    }
}

Key Details Explained

WriteJson Method Breakdown

  1. Reflect to get the Value property: We use reflection to access the Value property on the specific ValueObject<T> subclass instance—this works for any T since all subclasses inherit this property.
  2. Serialize the raw value: Instead of writing the entire ValueObject wrapper, we pass the underlying value directly to the serializer. This keeps the default serialization behavior for the raw type (e.g., Guid gets converted to its standard string format, strings get proper escaping, etc.).

How to Use the Converter

To activate the converter, add it to your Json.NET serializer settings:

var serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new ValueObjectOfTConverter());

// Example serialization
var sampleCustomer = new Customer(
    new CustomerId(Guid.Parse("f5ce21a5-a0d1-4888-8d22-6f484794ac7c")),
    new EmailAddress("some@email.com")
);

string serializedJson = JsonConvert.SerializeObject(sampleCustomer, serializerSettings);
// Output: {"Id":"f5ce21a5-a0d1-4888-8d22-6f484794ac7c","Email":"some@email.com"}

Important Notes

  • Ensure the Value property is public: Your existing ValueObject<T> code already has public T Value { get; }, which is required for the reflection step to work.
  • Preserves custom equality logic: The converter only handles serialization/deserialization—any custom Equals or GetHashCode logic in your ValueObject<T> or its subclasses remains intact.
  • Seamless deserialization: The ReadJson method wraps the raw JSON value back into a Value property wrapper, so Json.NET can use your subclass's constructor to recreate the object correctly.

This implementation will automatically handle every subclass of ValueObject<T> without any additional configuration per type.

内容的提问来源于stack exchange,提问作者kimsagro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:24:41