【问题标题】:.net System.Text.Json property name based serialization.net System.Text.Json 基于属性名的序列化
【发布时间】:2021-09-30 07:07:14
【问题描述】:

是否可以编写自定义转换器,根据属性名称进行序列化?

例如,如果属性名称以“DateTime”结尾,则保留 DateTime 类型属性的时间部分,否则丢弃时间部分。 转换器应该适用于所有类型,并且只针对 DateTime 属性。

您似乎无法在JsonConverter<T>Write 方法中检查属性名称。

【问题讨论】:

    标签: c# json .net serialization system.text.json


    【解决方案1】:

    是的,你可以通过反射来做到这一点。

    假设您有以下数据模型:

    class Example
    {
        public Guid Id { get; set; }
        public int InternalId { get; set; }
        public DateTime DateOnly { get; set; }
        public DateTime DateTime { get; set; }
    }
    
    • 如您所见,我们有两个 DateTime 属性
      • 其中一个以Time结尾
    • 我们还有另外两个属性只是为了确保我们是 在序列化过程中不会丢失类型信息

    您可以像这样针对整个对象创建一个 JsonConverter:

    class ExampleConverter : JsonConverter<Example>
    {
        public override Example Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            throw new NotImplementedException();
        }
    
        public override void Write(Utf8JsonWriter writer, Example value, JsonSerializerOptions options)
        {
            var objectProperties = value.GetType().GetProperties();
            var objectFieldNameValuePairs = new Dictionary<string, object>();
            foreach (var objectProperty in objectProperties)
            {
                if (objectProperty.PropertyType == typeof(DateTime))
                {
                    var datetimeFieldValue = (DateTime)objectProperty.GetValue(value);
                    var transformedValue = datetimeFieldValue.ToString(objectProperty.Name.EndsWith("Time") ? "g" : "d");
                    objectFieldNameValuePairs.Add(objectProperty.Name, transformedValue);
                }
                else
                    objectFieldNameValuePairs.Add(objectProperty.Name, objectProperty.GetValue(value));
            }
    
            writer.WriteStartObject();
    
            foreach (KeyValuePair<string,object> fieldNameAndValue in objectFieldNameValuePairs)
            {
                writer.WritePropertyName(fieldNameAndValue.Key);
                JsonSerializer.Serialize(writer, fieldNameAndValue.Value, options);
            }
    
            writer.WriteEndObject();
        }
    }
    
    • 首先我们得到Example的所有属性
    • 然后我们遍历它们
    • 如果属性包含DateTime,那么根据您提供的启发式,我们使用不同的date formatting string
      • 否则我们不会进行任何转换
    • 最后我们手动序列化Dictionary

    用法

    var example = new Example { DateOnly = DateTime.UtcNow, DateTime = DateTime.UtcNow, Id = Guid.NewGuid(), InternalId = 100 };
    var serializedExample = JsonSerializer.Serialize(example, new JsonSerializerOptions { Converters = { new ExampleConverter() } });
    
    Console.WriteLine(serializedExample);
    

    输出

    {
       "Id":"eddd0620-b27c-4041-bd28-af6bfbd70583",
       "InternalId":100,
       "DateOnly":"7/23/2021",
       "DateTime":"7/23/2021 7:10 AM"
    }
    

    更新让转换器更通用

    正如在 cmets 部分中所讨论的,转换器不应绑定到特定的类。它应该能够处理任何类。不幸的是,我们不能将object 指定为JsonConverter 的类型参数。

    另一方面,当我们从转换器创建实例时,我们可以接收类型参数。所以,这里是修改后的转换器代码:

    class DateTimeConverter<T> : JsonConverter<T>
    {
        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            throw new NotImplementedException();
        }
    
        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            PropertyInfo[] props = value.GetType().GetProperties();
            Dictionary<string, object> data = new Dictionary<string, object>();
            foreach (var prop in props)
            {
                if (prop.PropertyType == typeof(DateTime))
                {
                    var date = (DateTime)prop.GetValue(value);
                    data.Add(prop.Name, date.ToString(prop.Name.EndsWith("Time") ? "g" : "d"));
                }
                else
                    data.Add(prop.Name, prop.GetValue(value));
            }
    
            writer.WriteStartObject();
    
            foreach (KeyValuePair<string,object> item in data)
            {
                writer.WritePropertyName(item.Key);
                JsonSerializer.Serialize(writer, item.Value, options);
            }
    
            writer.WriteEndObject();
        }
    }
    

    用法

    var ex = new Example
    {
        DateOnly = DateTime.UtcNow,
        DateTime = DateTime.UtcNow,
        Id = Guid.NewGuid(),
        InternalId = 100
    };
    var aex = new AnotherExample
    {
        CreationDate = DateTime.UtcNow,
        Description = "Testing"
    };
    var options = new JsonSerializerOptions
    {
        Converters =
        {
            new DateTimeConverter<Example>(),
            new DateTimeConverter<AnotherExample>()
        }
    };
    
    Console.WriteLine(JsonSerializer.Serialize(ex, options));
    Console.WriteLine(JsonSerializer.Serialize(aex, options));
    

    已知限制:此转换器不能用于匿名类型。

    【讨论】:

    • 感谢您的回答,我编辑了我的问题以澄清。您的解决方案适用于特定类型的“示例”,但我想要一个适用于所有类型的解决方案。我想我正在寻找 JsonConverter 之类的东西。将通用参数更改为“对象”会起作用,但这会阻止我使用其他自定义转换器?
    • @zyq 序列化逻辑足够通用,可以处理任何类型的对象。因此,如果您将 JsonConverter 的 T 类型更改为 Object 即可。您可以注册多个转换器,而不仅仅是一个。 Options 定义了一个 Converters 集合。
    • 其实我只是通过将JsonConverter&lt;Example&gt;更改为JsonConverter&lt;Object&gt;来尝试您的代码,转换器没有被调用。
    • @zyq 是的,你是对的,它不起作用。我找到了一个替代解决方案,所以我已经更新了我的帖子,请检查一下。
    • 我希望我的所有类型的 Datetime 属性都以这种方式进行转换。我可以调整它以使用工厂模式转换器,这样我就不需要手动注册所有类型,但问题是通过使用这种方法,这个转换器将用于所有类型。我将无法使用其他针对自定义类型的转换器。
    猜你喜欢
    • 2023-02-01
    • 1970-01-01
    • 2020-08-19
    • 2018-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多