如何正确将格式异常的JSON字符串反序列化为C#类?
问题描述
给定如下JSON字符串:
{ "number": "1", "optionsList": [ "{\"answer\":\"42\", \"options\":\"washington\"}", "{\"answer\":\"65\", \"options\":\"new england\"}" ] }
其中optionsList的每个元素是JSON字符串而非JSON对象,无法修改原始JSON,需要将其正确反序列化为已定义的C#类:
Root.cs
public class Root { public Root() { Number = ""; OptionsList = new List<OptionsList>(); } [JsonProperty(PropertyName = "number")] public string Number { get; internal set; } [JsonProperty(PropertyName = "optionsList")] public List<OptionsList> OptionsList { get; internal set; } }
OptionsList.cs
public class OptionsList { [JsonProperty(PropertyName = "answer")] public string Answer { get; internal set; } [JsonProperty(PropertyName = "options")] public string Options { get; internal set; } }
使用JsonConvert.DeserializeObject<T>(response)反序列化时抛出异常:
---> Newtonsoft.Json.JsonSerializationException: Error converting value "{\"answer\":\"42\",\"options\":\"washington\"}" to type 'Types.OptionsList'. Path 'OptionsList[0]', line 1, position 1947. ---> System.ArgumentException: Could not cast or convert from System.String to Types.OptionsList. at Newtonsoft.Json.Utilities.ConvertUtils.EnsureTypeAssignable(Object value, Type initialType, Type targetType) at Newtonsoft.Json.Utilities.ConvertUtils.ConvertOrCast(Object initialValue, CultureInfo culture, Type targetType) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType) --- End of inner exception stack trace --- at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonConverter[] converters) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonConverter[] converters)
解决方案
方法1:自定义JsonConverter处理嵌套JSON字符串
这是最优雅的解决方案,给OptionsList类型创建自定义转换器,自动将JSON字符串反序列化为对象:
public class NestedJsonConverter : JsonConverter<OptionsList> { public override OptionsList ReadJson(JsonReader reader, Type objectType, OptionsList existingValue, bool hasExistingValue, JsonSerializer serializer) { // 读取原始JSON字符串并反序列化为OptionsList对象 var jsonString = reader.Value.ToString(); return JsonConvert.DeserializeObject<OptionsList>(jsonString); } public override void WriteJson(JsonWriter writer, OptionsList value, JsonSerializer serializer) { // 若需要序列化回JSON,将对象转为JSON字符串输出 var jsonString = JsonConvert.SerializeObject(value); writer.WriteValue(jsonString); } }
方式A:反序列化时传入转换器
var settings = new JsonSerializerSettings(); settings.Converters.Add(new NestedJsonConverter()); var deserializedResponse = JsonConvert.DeserializeObject<Root>(response, settings);
方式B:在类上标记特性(更简洁)
修改OptionsList类,直接绑定转换器:
[JsonConverter(typeof(NestedJsonConverter))] public class OptionsList { [JsonProperty(PropertyName = "answer")] public string Answer { get; internal set; } [JsonProperty(PropertyName = "options")] public string Options { get; internal set; } }
之后直接调用原反序列化代码即可:
var deserializedResponse = JsonConvert.DeserializeObject<Root>(response);
方法2:先反序列化为中间类再转换
如果不想编写转换器,可以定义中间类接收原始结构,再手动转换为目标类型:
// 中间类,匹配原始JSON的结构 public class RootIntermediate { [JsonProperty(PropertyName = "number")] public string Number { get; set; } [JsonProperty(PropertyName = "optionsList")] public List<string> OptionsList { get; set; } }
分两步处理:
// 第一步:反序列化为中间类 var intermediate = JsonConvert.DeserializeObject<RootIntermediate>(response); // 第二步:转换为目标Root类 var result = new Root { Number = intermediate.Number, OptionsList = intermediate.OptionsList .Select(jsonStr => JsonConvert.DeserializeObject<OptionsList>(jsonStr)) .ToList() };
方法3:在Root类中处理(不推荐)
这种方式会破坏类的单一职责,且需要处理反序列化顺序问题,仅作为备选方案:
修改Root类的OptionsList属性,在setter中手动转换,但需要注意Json.NET会先尝试将字符串转为OptionsList,仍会触发异常,因此需要配合中间转换逻辑,不如前两种方案可靠。
内容的提问来源于stack exchange,提问作者Jorge
相关产品推荐
相关产品推荐

