如何利用内联XSD在运行时将XML反序列化为POCO
问题
在将Azure DevOps的REST响应反序列化为多个类的过程中,JSON响应的某一属性包含带有内联Schema信息的XML。该Schema并非固定,因为它包含用户生成的列名和值组成的表格(以XML形式呈现),因此编译前无法确定反序列化目标对象的结构。请问如何将内联Schema转换为自定义反序列化器可使用的运行时生成对象结构?或者这种做法是否正确?
示例XML
<NewDataSet> <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale=""> <xs:complexType> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element name="Table1"> <xs:complexType> <xs:sequence> <xs:element name="status" type="xs:string" minOccurs="0" /> <xs:element name="petId" type="xs:string" minOccurs="0" /> <xs:element name="petName" type="xs:string" minOccurs="0" /> <xs:element name="petStatus" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema> <Table1> <status>available</status> <petId>0</petId> <petName>doggie</petName> <petStatus>available</petStatus> </Table1> <Table1> <status>pending</status> <petId>1</petId> <petName>cat</petName> <petStatus>pending</petStatus> </Table1> <Table1> <status>sold</status> <petId>2</petId> <petName>fish</petName> <petStatus>sold</petStatus> </Table1> </NewDataSet>
当前自定义反序列化器代码
public override NewDataSet? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // Read XML string and parse it to object of type Steps var xml = reader.GetString(); var serializer = new XmlSerializer(typeof(NewDataSet)); Log.Debug($"Microsoft.VSTS.TCM.LocalDataSource XML: {xml}"); using var stringReader = new StringReader(xml); return serializer.Deserialize(stringReader) as NewDataSet; }
解决方案
1. 使用DataSet动态解析(最优方案)
你提供的XML是标准的ADO.NET DataSet格式,DataSet的ReadXml方法可以自动识别内联Schema并完成数据解析,无需提前定义固定类:
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var xml = reader.GetString(); var dataSet = new DataSet(); using var stringReader = new StringReader(xml); dataSet.ReadXml(stringReader); // 解析后可直接通过Tables访问数据,也可转为字典列表方便后续处理 var targetTable = dataSet.Tables["Table1"]; var result = new List<Dictionary<string, object>>(); foreach (DataRow row in targetTable.Rows) { var rowDict = new Dictionary<string, object>(); foreach (DataColumn column in targetTable.Columns) { rowDict[column.ColumnName] = row[column] ?? DBNull.Value; } result.Add(rowDict); } return result; }
这种方案完全适配动态Schema,无需编译时确定结构,是处理此类场景的首选。
2. 使用XDocument手动解析(轻量方案)
如果不想依赖DataSet,可以用LINQ to XML手动遍历节点提取数据:
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var xml = reader.GetString(); var doc = XDocument.Parse(xml); // 跳过Schema节点,直接获取数据行 var dataRows = doc.Descendants().Where(node => node.Name.LocalName == "Table1"); var result = new List<Dictionary<string, string>>(); foreach (var row in dataRows) { var rowDict = row.Elements() .ToDictionary(element => element.Name.LocalName, element => element.Value); result.Add(rowDict); } return result; }
该方式更轻量,但需要自行处理数据类型转换,适合结构简单的场景。
3. 运行时生成类型(不推荐)
如果必须生成强类型对象,可以通过System.Reflection.Emit或第三方库根据Schema动态创建类,但这种做法复杂度高、维护成本大,性能也不如前两种方案,仅在有强类型绑定硬性需求时考虑。
原有做法的正确性
你当前用XmlSerializer反序列化到固定NewDataSet类的方式,仅适用于Schema完全固定的场景。一旦用户修改列名或新增列,代码会直接失效,因此原有做法不适用于动态Schema的场景,建议替换为上述动态解析方案。
内容的提问来源于stack exchange,提问作者Pebermynte Lars
相关产品推荐
相关产品推荐

