使用C# DataContractJsonSerializer反序列化含JSON的字符串属性遇XmlException
问题描述
我尝试反序列化一个包含名为Metadata的字符串属性的类,该属性的值本身是JSON内容。我不需要解析这段JSON内部的结构,只希望把它原封不动地作为字符串存入属性里。但Web服务返回的JSON格式我无法控制,现在反序列化时抛出异常:
XmlException: 预期命名空间为空的结束元素'metadata',却找到命名空间为空的元素'key'。
当前代码示例:
[DataContract] public class YourClass { [DataMember(Name = "metadata", IsRequired = false, EmitDefaultValue = false)] public string Metadata { get; set; } [DataMember(Name = "anotherProperty", IsRequired = false, EmitDefaultValue = false)] public int AnotherProperty { get; set; } } internal class Program { static void Main() { // Web服务返回的JSON字符串 string jsonString = "{\"metadata\":{\"key\":\"value\"}, \"anotherProperty\":42}"; using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString))) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(YourClass)); YourClass obj = (YourClass)serializer.ReadObject(stream); Console.WriteLine(obj.Metadata); } } }
解决方案
问题根源是DataContractJsonSerializer会自动解析metadata对应的JSON对象,而你需要它把这个对象当作原始字符串处理。下面是几种实用的实现方式:
方法一:用Newtonsoft.Json(Json.NET)实现(推荐)
这是最简便的方案,自定义转换器将JSON对象直接转为原始字符串:
using Newtonsoft.Json; using Newtonsoft.Json.Linq; public class YourClass { [JsonProperty("metadata")] public string Metadata { get; set; } [JsonProperty("anotherProperty")] public int AnotherProperty { get; set; } } internal class Program { static void Main() { string jsonString = "{\"metadata\":{\"key\":\"value\"}, \"anotherProperty\":42}"; YourClass obj = JsonConvert.DeserializeObject<YourClass>(jsonString, new JsonSerializerSettings { Converters = { new RawJsonConverter() } }); Console.WriteLine(obj.Metadata); // 输出: {"key":"value"} } } // 自定义转换器,把JSON对象转成原始字符串 public class RawJsonConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(string); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.StartArray) { JToken token = JToken.Load(reader); return token.ToString(Formatting.None); } return reader.Value?.ToString(); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { writer.WriteRawValue(value.ToString()); } }
方法二:修改DataContract属性类型(兼容原生序列化器)
如果必须用DataContractJsonSerializer,可以把Metadata的类型改为dynamic或JObject,反序列化后再转成字符串:
[DataContract] public class YourClass { [DataMember(Name = "metadata", IsRequired = false, EmitDefaultValue = false)] public dynamic Metadata { get; set; } [DataMember(Name = "anotherProperty", IsRequired = false, EmitDefaultValue = false)] public int AnotherProperty { get; set; } } // 反序列化后转换为字符串 YourClass obj = (YourClass)serializer.ReadObject(stream); string metadataStr = JsonConvert.SerializeObject(obj.Metadata); Console.WriteLine(metadataStr);
或者直接用JObject类型:
[DataContract] public class YourClass { [DataMember(Name = "metadata")] public JObject Metadata { get; set; } [DataMember(Name = "anotherProperty")] public int AnotherProperty { get; set; } } // 使用时转字符串 string metadataStr = obj.Metadata.ToString(Formatting.None);
方法三:自定义DataContract代理(无第三方依赖)
如果不想引入Newtonsoft.Json,可以实现IDataContractSurrogate来手动处理metadata字段,但这种方法比较繁琐:
using System.Runtime.Serialization; using System.Reflection; using Newtonsoft.Json.Linq; // 或自行实现JSON解析逻辑 public class MetadataSurrogate : IDataContractSurrogate { public Type GetDataContractType(Type type) => type; public object GetDeserializedObject(object obj, Type targetType) { if (targetType == typeof(YourClass)) { var yourObj = (YourClass)obj; // 重新解析原始JSON字符串,提取metadata的原始内容 JObject jsonObj = JObject.Parse(jsonString); yourObj.Metadata = jsonObj["metadata"].ToString(Formatting.None); return yourObj; } return obj; } // 其他接口方法默认实现 public object GetObjectToSerialize(object obj, Type targetType) => obj; public object GetCustomDataToExport(Type clrType, Type dataContractType) => null; public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType) => null; public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes) { } public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData) => null; public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit) => typeDeclaration; } // 使用方式 var settings = new DataContractJsonSerializerSettings { DataContractSurrogate = new MetadataSurrogate() }; DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(YourClass), settings);
内容的提问来源于stack exchange,提问作者NKatic
相关产品推荐
相关产品推荐

