如何用单个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
- Reflect to get the Value property: We use reflection to access the
Valueproperty on the specificValueObject<T>subclass instance—this works for anyTsince all subclasses inherit this property. - Serialize the raw value: Instead of writing the entire
ValueObjectwrapper, 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 haspublic T Value { get; }, which is required for the reflection step to work. - Preserves custom equality logic: The converter only handles serialization/deserialization—any custom
EqualsorGetHashCodelogic in yourValueObject<T>or its subclasses remains intact. - Seamless deserialization: The
ReadJsonmethod wraps the raw JSON value back into aValueproperty 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
相关产品推荐
相关产品推荐

