gpt4 book ai didi

c# - Json.NET - 序列化没有属性名称的通用类型包装器

转载 作者:行者123 更新时间:2023-11-30 12:55:21 25 4
gpt4 key购买 nike

我有一个通用类型,它包装了一个原始类型以赋予它值相等语义

public class ValueObject<T>
{
public T Value { get; }
public ValueObject(T value) => Value = value;

// various other equality members etc...
}

它的用法如下:

public class CustomerId : ValueObject<Guid>
{
public CustomerId(Guid value) : base(value) { }
}

public class EmailAddress : ValueObject<string>
{
public EmailAddress(string value) : base(value) { }
}

问题是在序列化如下类型时:

public class Customer
{
public CustomerId Id { get; }
public EmailAddress Email { get; }

public Customer(CustomerId id, EmailAddress email)
{
Id = id;
Email = email;
}
}

每个对象都继承自 ValueObject<T>包裹在 Value 中属性(property)(如预期)。例如

var customerId = new CustomerId(Guid.NewGuid());
var emailAddress = new EmailAddress("some@email.com");

var customer = new Customer(customerId, emailAddress);

var customerAsJson = JsonConvert.SerializeObject(customer, Formatting.Indented, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})

结果

{
"id": {
"value": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c"
},
"email": {
"value": "some@email.com"
}
}

有没有办法写一个自定义JsonConverter所以Value类型子类排除属性 ValueObject<T>这样上面的例子就会输出

{
"id": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c",
"email": "some@email.com"
}

我宁愿有一个 JsonConverter可以处理所有 ValueObject<T>而不是必须定义一个单独的 JsonConverter对于每个 ValueObject<T>子类

我的第一次尝试是

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)
{
// converts "f5ce21a5-a0d1-4888-8d22-6f484794ac7c" => "value": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c"
var existingJsonWrappedInValueProperty = new JObject(new JProperty(ValuePropertyName, JToken.Load(reader)));
return existingJsonWrappedInValueProperty.ToObject(objectType, serializer);
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// to implement
}

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

typeToCheck = typeToCheck.BaseType;
}

return false;
}
}

最佳答案

您可以使用 custom JsonConverter 来做到这一点类似于 Json.Net: Serialize/Deserialize property as a value, not as an object 中显示的那些。然而,由于 ValueObject<T>没有非通用方法来获取和设置 Value作为对象,您将需要使用反射。

这是一种方法:

class ValueConverter : JsonConverter
{
static Type GetValueType(Type objectType)
{
return objectType
.BaseTypesAndSelf()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueObject<>))
.Select(t => t.GetGenericArguments()[0])
.FirstOrDefault();
}

public override bool CanConvert(Type objectType)
{
return GetValueType(objectType) != null;
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// You need to decide whether a null JSON token results in a null ValueObject<T> or
// an allocated ValueObject<T> with a null Value.
if (reader.SkipComments().TokenType == JsonToken.Null)
return null;
var valueType = GetValueType(objectType);
var value = serializer.Deserialize(reader, valueType);

// Here we assume that every subclass of ValueObject<T> has a constructor with a single argument, of type T.
return Activator.CreateInstance(objectType, value);
}

const string ValuePropertyName = nameof(ValueObject<object>.Value);

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var valueProperty = contract.Properties.Where(p => p.UnderlyingName == ValuePropertyName).Single();
// You can simplify this to .Single() if ValueObject<T> has no other properties:
// var valueProperty = contract.Properties.Single();
serializer.Serialize(writer, valueProperty.ValueProvider.GetValue(value));
}
}

public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}

public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
}

然后您可以将转换器直接应用于 ValueType<T>像这样:

[JsonConverter(typeof(ValueConverter))]
public class ValueObject<T>
{
// Remainder unchanged
}

或者改为在设置中应用它:

var settings = new JsonSerializerSettings
{
Converters = { new ValueConverter() },
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var customerAsJson = JsonConvert.SerializeObject(customer, Formatting.Indented, settings);

工作示例 .Net fiddle #1 here .

或者,您可以考虑添加一个非泛型方法来访问作为 object 的值。 ,例如像这样:

public interface IHasValue
{
object GetValue(); // A method rather than a property to ensure the non-generic value is never serialized directly.
}

public class ValueObject<T> : IHasValue
{
public T Value { get; }
public ValueObject(T value) => Value = value;

// various other equality members etc...

#region IHasValue Members

object IHasValue.GetValue() => Value;

#endregion
}

加上这个, WriteJson() 变得简单多了:

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, ((IHasValue)value).GetValue());
}

工作示例 .Net fiddle #2 here .

注意事项:

  • ReadJson()假设 Value<T> 的每个子类有一个公共(public)构造函数接受一个类型为 T 的参数.

  • 将转换器直接应用于 ValueType<T>使用 [JsonConverter(typeof(ValueConverter))] 会有更好的表现,因为 CanConvert 永远不需要被调用。参见 Performance Tips: JsonConverters 了解详情。

  • 您需要决定如何处理 null JSON token 。它是否会导致空 ValueType<T> ,或分配的 ValueType<T>Value ?

  • ValueType<T> 的第二个版本中我实现了 IHasValue.GetValue()明确地阻止在 ValueType<T> 的实例的情况下使用它用于静态类型代码。

  • 如果您真的只想将转换器应用于类型子类化 ValueObject<T> 而不是 ValueObject<T>本身,在 GetValueType(Type objectType)添加调用 .Skip(1) :

    static Type GetValueType(Type objectType)
    {
    return objectType
    .BaseTypesAndSelf()
    .Skip(1) // Do not apply the converter to ValueObject<T> when not subclassed
    .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueObject<>))
    .Select(t => t.GetGenericArguments()[0])
    .FirstOrDefault();
    }

    然后在JsonSerializerSettings.Converters中应用转换器而不是直接到ValueObject<T> .

关于c# - Json.NET - 序列化没有属性名称的通用类型包装器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50313978/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com