实现带引用循环处理与自定义转换器的类XML序列化
问题描述
已用Json.NET完成City类的JSON序列化,现在需要实现相同逻辑的XML序列化,但遇到两个核心问题:
- 引用循环处理:使用
XmlAttributeOverrides忽略某类时会忽略所有该类型实例,无法精准控制子类中的部分字段 - 自定义字段转换:无法拦截
NetTopologySuite.Geometries.Point类型字段的序列化,实现类似JSON的EWKT格式转换逻辑
(注:无法修改第三方DLL中的City类及其Location字段)
现有实现代码
重写XML默认配置的尝试
List<City> cities = {populate the class}; var typCities= cities.GetType(); XmlAttributeOverrides xmlOvrds = new XmlAttributeOverrides(); Type typRdInfo = typeof(RoadInfo); xmlOvrds.Add(typRdInfo, "Buidling", new XmlAttributes { XmlIgnore = true }); xmlOvrds.Add(typRdInfo, "Road", new XmlAttributes { XmlIgnore = true }); XmlSerializer xmlSerializer = new(typCities, xmlOvrds);
City类示例结构
public partial class City { public int CityId { get; set; } [MinLength(3, ErrorMessage = "Minimum {1} characters allowed")] public string CityCode { get; set; } [StringLength(128, ErrorMessage = "Maximum 128 characters allowed")] [DisplayFormat(NullDisplayText = "Null")] public string? Remarks { get; set; } [DisplayFormat(NullDisplayText = "Null")] public NetTopologySuite.Geometries.Point? Location { get; set; } }
已实现的Json.NET序列化代码
Newtonsoft.Json.Formatting fmt = Newtonsoft.Json.Formatting.Indented; // pretty print JsonSerializerSettings sett = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, // 忽略自引用循环 Converters = new List<JsonConverter> { new PointNsJsonConverter() }, // 自定义NTS转换器 }; string jsonCont = JsonConvert.SerializeObject(cities, fmt, sett); public class PointNsJsonConverter : Newtonsoft.Json.JsonConverter<NetTopologySuite.Geometries.Point> { public override NetTopologySuite.Geometries.Point? ReadJson(JsonReader reader, Type objectType, NetTopologySuite.Geometries.Point? pnt, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer) { // 示例输入:SRID=4326;POINT Z(100 45 0) string rdrVal = (string)reader.Value; if (string.IsNullOrWhiteSpace(rdrVal)) return null; Regex MyRegex = new Regex(@"SRID=(?<srid>\d*);POINT Z\((?<x>[-+]?\d{1,3}) (?<y>[-+]?\d{1,3}) (?<z>[-+]?\d{1,3})\)", RegexOptions.IgnoreCase); Match ms = MyRegex.Match(rdrVal); if (ms.Success) { int srid = Convert.ToInt32(ms.Groups["srid"].Value); double x = Convert.ToDouble(ms.Groups["x"].Value); double y = Convert.ToDouble(ms.Groups["y"].Value); double z = Convert.ToDouble(ms.Groups["z"].Value); return new NetTopologySuite.Geometries.Point(x, y, z) { SRID = srid }; } else { return null; } } public override void WriteJson(JsonWriter writer, NetTopologySuite.Geometries.Point? pnt, Newtonsoft.Json.JsonSerializer serializer) { if (pnt is null) return; // 输出EWKT格式:SRID=4326;POINT Z(100 45 0) string ewkt = string.Format("SRID=4326;{0}", pnt.ToString()); writer.WriteValue(ewkt); } public override bool CanRead => true; public override bool CanWrite => true; }
解决方案
1. 精准处理引用循环:使用IXmlSerializable包装类
XmlAttributeOverrides无法精准控制单个字段的引用忽略,我们可以为包含循环引用的类创建包装类,实现IXmlSerializable接口手动控制序列化逻辑,只忽略指定字段而非整个类型。
示例(针对RoadInfo类):
public class RoadInfoXmlWrapper : IXmlSerializable { public RoadInfo InnerRoadInfo { get; set; } public RoadInfoXmlWrapper() {} public RoadInfoXmlWrapper(RoadInfo roadInfo) => InnerRoadInfo = roadInfo; public XmlSchema GetSchema() => null; public void ReadXml(XmlReader reader) { reader.ReadStartElement(); InnerRoadInfo = new RoadInfo(); // 跳过Buidling和Road字段,只序列化其他必要字段 while (reader.NodeType != XmlNodeType.EndElement) { if (reader.Name != "Buidling" && reader.Name != "Road") { PropertyInfo prop = typeof(RoadInfo).GetProperty(reader.Name); prop?.SetValue(InnerRoadInfo, reader.ReadElementContentAsObject()); } else { reader.Skip(); } } reader.ReadEndElement(); } public void WriteXml(XmlWriter writer) { // 只序列化需要的字段,忽略Buidling和Road foreach (PropertyInfo prop in typeof(RoadInfo).GetProperties()) { if (prop.Name != "Buidling" && prop.Name != "Road") { object value = prop.GetValue(InnerRoadInfo); if (value != null) { writer.WriteElementString(prop.Name, value.ToString()); } } } } }
2. 自定义Point类型的XML序列化:实现XmlConverter或包装类
针对NetTopologySuite.Geometries.Point,有两种方式实现EWKT格式转换:
方法一:使用XmlConverter(推荐,类似Json.NET的Converter)
适用于.NET Framework 4.5+或.NET Core及以上版本:
public class PointXmlConverter : XmlConverter { public override bool CanConvert(Type type) { return type == typeof(NetTopologySuite.Geometries.Point); } public override object ReadXml(XmlReader reader, Type type, object existingValue, XmlSerializer serializer) { string ewkt = reader.ReadElementContentAsString(); if (string.IsNullOrWhiteSpace(ewkt)) return null; Regex regex = new Regex(@"SRID=(?<srid>\d*);POINT Z\((?<x>[-+]?\d{1,3}) (?<y>[-+]?\d{1,3}) (?<z>[-+]?\d{1,3})\)", RegexOptions.IgnoreCase); Match match = regex.Match(ewkt); if (match.Success) { int srid = int.Parse(match.Groups["srid"].Value); double x = double.Parse(match.Groups["x"].Value); double y = double.Parse(match.Groups["y"].Value); double z = double.Parse(match.Groups["z"].Value); return new NetTopologySuite.Geometries.Point(x, y, z) { SRID = srid }; } return null; } public override void WriteXml(XmlWriter writer, object value, XmlSerializer serializer) { var point = value as NetTopologySuite.Geometries.Point; if (point == null) return; string ewkt = $"SRID=4326;{point.ToString()}"; writer.WriteString(ewkt); } }
方法二:使用IXmlSerializable包装类(兼容旧版本.NET)
public class PointXmlWrapper : IXmlSerializable { public NetTopologySuite.Geometries.Point InnerPoint { get; set; } public PointXmlWrapper() {} public PointXmlWrapper(NetTopologySuite.Geometries.Point point) => InnerPoint = point; public XmlSchema GetSchema() => null; public void ReadXml(XmlReader reader) { string ewkt = reader.ReadElementContentAsString(); if (string.IsNullOrWhiteSpace(ewkt)) return; Regex regex = new Regex(@"SRID=(?<srid>\d*);POINT Z\((?<x>[-+]?\d{1,3}) (?<y>[-+]?\d{1,3}) (?<z>[-+]?\d{1,3})\)", RegexOptions.IgnoreCase); Match match = regex.Match(ewkt); if (match.Success) { int srid = int.Parse(match.Groups["srid"].Value); double x = double.Parse(match.Groups["x"].Value); double y = double.Parse(match.Groups["y"].Value); double z = double.Parse(match.Groups["z"].Value); InnerPoint = new NetTopologySuite.Geometries.Point(x, y, z) { SRID = srid }; } } public void WriteXml(XmlWriter writer) { if (InnerPoint == null) return; string ewkt = $"SRID=4326;{InnerPoint.ToString()}"; writer.WriteString(ewkt); } }
整合配置创建XmlSerializer
将自定义转换器/包装类通过XmlAttributeOverrides关联到目标字段:
XmlAttributeOverrides overrides = new XmlAttributeOverrides(); // 配置Point字段的自定义序列化 XmlAttributes cityLocationAttrs = new XmlAttributes(); cityLocationAttrs.XmlConverter = new PointXmlConverter(); // 若用包装类则替换为XmlElements.Add(new XmlElementAttribute("Location", typeof(PointXmlWrapper))) overrides.Add(typeof(City), "Location", cityLocationAttrs); // 配置RoadInfo的引用循环处理 XmlAttributes roadInfoAttrs = new XmlAttributes(); roadInfoAttrs.XmlElements.Add(new XmlElementAttribute("RoadInfo", typeof(RoadInfoXmlWrapper))); overrides.Add(typeof(YourParentClass), "RoadInfo", roadInfoAttrs); // 创建最终的序列化器 XmlSerializer serializer = new XmlSerializer(typeof(List<City>), overrides);
内容的提问来源于stack exchange,提问作者sinDizzy
相关产品推荐
相关产品推荐

