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

如何实现可通过静态Create方法按参数名称映射反序列化值对象的通用Newtonsoft.Json转换器

如何实现可通过静态Create方法按参数名称映射反序列化值对象的通用Newtonsoft.Json转换器

我太懂你这种头疼的感觉了——用私有构造+静态Create方法的方式实现值对象,确实能保证对象的完整性,但碰上Newtonsoft.Json反序列化就卡壳了,尤其是想做个通用转换器不用每个值对象都写专属逻辑的话。不过别慌,咱们可以实现一个通用的JsonConverter,它能自动找到值对象的静态Create方法,把JSON里的属性按名称映射到方法参数,最终调用Create方法实例化对象。

核心思路

核心逻辑就是靠反射完成几件关键事:

  • 定位目标值对象类型里的公开静态Create方法
  • 解析JSON中的所有属性键值对
  • 忽略大小写匹配JSON属性与Create方法的参数(适配JSON驼峰命名和C#帕斯卡命名的差异)
  • 转换参数类型并调用Create方法生成实例

通用转换器实现代码

public class ValueObjectConverter : JsonConverter<ValueObject>
{
    public override bool CanConvert(Type objectType)
    {
        // 仅处理继承自ValueObject的非抽象类型
        return typeof(ValueObject).IsAssignableFrom(objectType) && !objectType.IsAbstract;
    }

    public override void WriteJson(JsonWriter writer, ValueObject value, JsonSerializer serializer)
    {
        // 序列化直接用默认逻辑,值对象的公开属性都能正常输出
        var jo = JObject.FromObject(value);
        jo.WriteTo(writer);
    }

    public override ValueObject ReadJson(JsonReader reader, Type objectType, ValueObject existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        // 将JSON读取为JObject,方便遍历属性
        var jo = JObject.Load(reader);

        // 查找目标类型的静态Create方法(默认匹配唯一同名且返回当前类型的方法)
        var createMethod = objectType.GetMethods(BindingFlags.Public | BindingFlags.Static)
            .FirstOrDefault(m => m.Name == "Create" && m.ReturnType == objectType);

        if (createMethod == null)
            throw new JsonSerializationException($"类型 {objectType.FullName} 未找到符合要求的公开静态Create方法");

        var parameters = createMethod.GetParameters();
        var parameterValues = new object[parameters.Length];

        foreach (var param in parameters)
        {
            // 忽略大小写匹配JSON属性
            var property = jo.Properties()
                .FirstOrDefault(p => string.Equals(p.Name, param.Name, StringComparison.OrdinalIgnoreCase));

            if (property == null)
            {
                // 参数有默认值则用默认值,否则抛出缺失必填参数的异常
                if (param.HasDefaultValue)
                {
                    parameterValues[param.Position] = param.DefaultValue;
                }
                else
                {
                    throw new JsonSerializationException($"类型 {objectType.FullName} 的Create方法缺少必填参数 {param.Name}");
                }
            }
            else
            {
                // 将JSON值转换为参数对应的类型
                parameterValues[param.Position] = property.Value.ToObject(param.ParameterType, serializer);
            }
        }

        // 调用Create方法生成值对象实例
        var instance = createMethod.Invoke(null, parameterValues);
        return (ValueObject)instance;
    }
}

使用方式

你可以通过两种方式注册这个转换器:

  1. 全局注册:让所有继承自ValueObject的类型自动使用该转换器,适合项目中统一配置:
var settings = new JsonSerializerSettings
{
    Converters = new List<JsonConverter> { new ValueObjectConverter() },
    TypeNameHandling = TypeNameHandling.Auto // 对应你示例中的$type字段,确保反序列化时识别具体类型
};

// 反序列化示例
var json = @"{""$type"":""Entities.Individual, myassembly"",""name"":{""$type"":""ValueObjects.PersonName, myassembly"",""firstName"": ""John"",""middleName"": """",""lastName"": ""Wayne""},""citizenship"": ""American""}";
var individual = JsonConvert.DeserializeObject<Individual>(json, settings);
  1. 特性标记:仅给特定值对象使用该转换器,灵活性更高:
[JsonConverter(typeof(ValueObjectConverter))]
public sealed class PersonName : ValueObject
{
    // 你的值对象实现代码...
}

注意事项

  • 转换器默认假设每个值对象只有一个符合要求的Create方法,如果存在多个重载,你需要在代码中添加更严格的筛选逻辑(比如根据参数数量、类型匹配)。
  • 若Create方法包含必填参数,JSON中必须提供对应属性;带默认值的可选参数可省略,转换器会自动使用参数默认值。
  • 序列化无需额外处理,值对象的公开可读属性能被Newtonsoft.Json正常序列化。
  • 使用TypeNameHandling.Auto时,建议结合SerializationBinder限制允许反序列化的类型,避免安全风险。

备注:内容来源于stack exchange,提问作者user23137927

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 12:59:37