为Dictionary自定义DateRange类型的JsonConverter遇序列化异常问题
问题根源与解决方案
你的异常核心原因是JSON规范不允许对象作为字典的键,JSON对象的键必须是字符串类型。你当前在WriteAsPropertyName方法中直接输出了一个嵌套对象(WriteStartObject/WriteEndObject),这违反了JSON语法,直接导致序列化时出现"数据写入过多或过少"的错误。同时你的读取逻辑也完全不符合JSON的结构解析规则。
修正后的DateRangeConverter代码
public class DateRangeConverter : JsonConverter<DateRange> { // 选一个不会出现在ISO日期格式里的分隔符 private const string DateSeparator = "|"; // 处理DateRange作为普通JSON值的读取(非字典键场景) public override DateRange? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("DateRange必须以对象形式存在"); DateTime? begin = null; DateTime? end = null; while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) break; if (reader.TokenType == JsonTokenType.PropertyName) { var propName = reader.GetString(); reader.Read(); switch (propName) { case nameof(DateRange.Begin): begin = reader.GetDateTime(); break; case nameof(DateRange.End): end = reader.GetDateTime(); break; default: reader.Skip(); // 跳过未知属性 break; } } } if (begin == null || end == null) throw new JsonException("DateRange必须包含Begin和End字段"); return new DateRange { Begin = begin.Value, End = end.Value }; } // 处理DateRange作为字典键的读取逻辑 public override DateRange ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.String) throw new JsonException("DateRange字典键必须是字符串格式"); var keyStr = reader.GetString(); if (string.IsNullOrEmpty(keyStr)) throw new JsonException("DateRange字典键不能为空"); var dateParts = keyStr.Split(new[] { DateSeparator }, StringSplitOptions.RemoveEmptyEntries); if (dateParts.Length != 2) throw new JsonException($"无效的DateRange键格式:{keyStr}"); if (!DateTime.TryParse(dateParts[0], out var begin) || !DateTime.TryParse(dateParts[1], out var end)) throw new JsonException($"无法从键解析DateRange:{keyStr}"); return new DateRange { Begin = begin, End = end }; } // 处理DateRange作为字典键的序列化逻辑 public override void WriteAsPropertyName(Utf8JsonWriter writer, DateRange value, JsonSerializerOptions options) { // 将DateRange转为ISO日期拼接的字符串,符合JSON键的字符串要求 var keyStr = $"{value.Begin:o}{DateSeparator}{value.End:o}"; writer.WritePropertyName(keyStr); } // 处理DateRange作为普通JSON值的序列化逻辑 public override void Write(Utf8JsonWriter writer, DateRange value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString(nameof(DateRange.Begin), value.Begin); writer.WriteString(nameof(DateRange.End), value.End); writer.WriteEndObject(); } }
关键改动说明
- WriteAsPropertyName:把DateRange转换为
"BeginISO日期|EndISO日期"格式的字符串,严格遵循JSON键必须是字符串的规则。 - ReadAsPropertyName:读取字符串键后,通过分隔符分割并解析出Begin和End两个日期值。
- Read方法:重新实现了普通对象场景的读取逻辑,正确遍历JSON对象的属性并赋值。
- 移除了原来错误的手动解析日期字段的逻辑,改用框架自带的日期解析方法,避免自定义解析的bug。
使用示例
在序列化/反序列化时注册转换器即可:
var jsonOptions = new JsonSerializerOptions(); jsonOptions.Converters.Add(new DateRangeConverter()); // 序列化字典 var dateRangeDict = new Dictionary<DateRange, List<MyObject>>(); // 添加数据... var jsonStr = JsonSerializer.Serialize(dateRangeDict, jsonOptions); // 反序列化 var deserializedDict = JsonSerializer.Deserialize<Dictionary<DateRange, List<MyObject>>>(jsonStr, jsonOptions);
内容的提问来源于stack exchange,提问作者CrsB1
相关产品推荐
相关产品推荐

