You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现带引用循环处理与自定义转换器的类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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 14:14:55