如何构建C#类以反序列化含多类型属性的JSON数据
如何让C#类的属性同时支持字符串和整数类型的JSON反序列化?
当然可以!要处理这种JSON中同一字段既可能是字符串又可能是整数的情况,最可靠的方式是通过自定义JSON转换器来实现,或者用私有中间属性做过渡处理。下面我给你两种主流JSON库的解决方案,以及一个简化的替代方案:
方法1:使用Newtonsoft.Json(Json.NET)的自定义转换器
这是.NET生态里最常用的JSON库,自定义转换器可以完美处理类型兼容问题。
首先创建一个转换器,用来把月份名称(比如"may")和数字(比如5)统一转换成整数类型:
public class MonthConverter : JsonConverter<int> { // 存储月份名称到数字的映射,忽略大小写 private static readonly Dictionary<string, int> _monthNames = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) { ["january"] = 1, ["february"] = 2, ["march"] = 3, ["april"] = 4, ["may"] = 5, ["june"] = 6, ["july"] = 7, ["august"] = 8, ["september"] = 9, ["october"] = 10, ["november"] = 11, ["december"] = 12 }; public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer) { // 处理字符串类型的月份 if (reader.TokenType == JsonToken.String) { var monthStr = reader.Value.ToString(); if (_monthNames.TryGetValue(monthStr, out int month)) return month; throw new JsonSerializationException($"无效的月份名称:{monthStr}"); } // 处理整数类型的月份 else if (reader.TokenType == JsonToken.Integer) { var monthNum = Convert.ToInt32(reader.Value); if (monthNum is >=1 and <=12) return monthNum; throw new JsonSerializationException($"无效的月份数字:{monthNum}"); } throw new JsonSerializationException($"不支持的Token类型:{reader.TokenType}"); } public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer) { // 序列化时可以选择输出数字或者名称,这里默认输出数字 writer.WriteValue(value); // 如果要输出月份名称,替换成下面这行: // writer.WriteValue(_monthNames.First(kv => kv.Value == value).Key); } }
然后定义你的SomeObject类,给BirthMonth属性加上转换器特性:
public class SomeObject { public string Name { get; set; } [JsonConverter(typeof(MonthConverter))] public int BirthMonth { get; set; } }
别忘了定义包裹它的外层类(因为你的JSON是嵌套结构):
public class Wrapper { public SomeObject SomeObject { get; set; } }
现在测试反序列化两种格式的JSON:
// 格式1:字符串月份 var json1 = "{ \"SomeObject\" : { \"name\" : \"something\", \"birthMonth\" : \"may\" } }"; var result1 = JsonConvert.DeserializeObject<Wrapper>(json1); // 格式2:整数月份 var json2 = "{ \"SomeObject\" : { \"name\" : \"something\", \"birthMonth\" : 5 } }"; var result2 = JsonConvert.DeserializeObject<Wrapper>(json2);
方法2:使用System.Text.Json(.NET Core/.NET 5+内置)的自定义转换器
如果你用的是.NET Core或更高版本的内置JSON库,同样可以自定义转换器:
public class MonthConverter : JsonConverter<int> { private static readonly Dictionary<string, int> _monthNames = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) { ["january"] = 1, ["february"] = 2, ["march"] = 3, ["april"] = 4, ["may"] = 5, ["june"] = 6, ["july"] = 7, ["august"] = 8, ["september"] = 9, ["october"] = 10, ["november"] = 11, ["december"] = 12 }; public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.String) { var monthStr = reader.GetString(); if (_monthNames.TryGetValue(monthStr, out int month)) return month; throw new JsonException($"无效的月份名称:{monthStr}"); } else if (reader.TokenType == JsonTokenType.Number) { if (reader.TryGetInt32(out int monthNum) && monthNum is >=1 and <=12) return monthNum; throw new JsonException($"无效的月份数字:{reader.GetInt32()}"); } throw new JsonException($"不支持的Token类型:{reader.TokenType}"); } public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) { writer.WriteNumberValue(value); // 要输出月份名称的话,替换成: // writer.WriteStringValue(_monthNames.First(kv => kv.Value == value).Key); } }
然后修改SomeObject类的特性(注意是System.Text.Json的特性):
using System.Text.Json.Serialization; public class SomeObject { public string Name { get; set; } [JsonConverter(typeof(MonthConverter))] public int BirthMonth { get; set; } }
同样需要外层Wrapper类,测试代码:
var json1 = "{ \"SomeObject\" : { \"name\" : \"something\", \"birthMonth\" : \"may\" } }"; var result1 = JsonSerializer.Deserialize<Wrapper>(json1); var json2 = "{ \"SomeObject\" : { \"name\" : \"something\", \"birthMonth\" : 5 } }"; var result2 = JsonSerializer.Deserialize<Wrapper>(json2);
替代方案:用私有中间属性处理(无需自定义转换器)
如果不想写单独的转换器,也可以用一个私有属性来接收原始值,再转换到BirthMonth:
using System.Globalization; using Newtonsoft.Json; // 或者用System.Text.Json的[JsonPropertyName] public class SomeObject { public string Name { get; set; } public int BirthMonth { get; set; } // 用这个私有属性接收JSON里的原始值 [JsonProperty("birthMonth")] // 如果用System.Text.Json,换成[JsonPropertyName("birthMonth")] private object BirthMonthRaw { set { switch (value) { case int num: if (num is >=1 and <=12) BirthMonth = num; else throw new ArgumentException($"无效的月份数字:{num}"); break; case string str: // 用DateTime内置方法解析月份名称 if (DateTime.TryParseExact(str, "MMMM", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) BirthMonth = date.Month; else throw new ArgumentException($"无效的月份名称:{str}"); break; default: throw new ArgumentException($"不支持的birthMonth类型:{value.GetType()}"); } } } }
这种方式更轻量,适合简单场景,但只处理反序列化逻辑,序列化还是用BirthMonth的整数输出。
内容的提问来源于stack exchange,提问作者LKon524
相关产品推荐
相关产品推荐

