最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Serialize Class to XML with ReferenceLoopHandling and Custom Converter - Stack Overflow

programmeradmin0浏览0评论

I am converting a class to JSON and XML. The Json conversion for the class cities is done and is shown below. I am trying to do the same with an XML output, but see that these options for field custom conversion and loop handling are not native to the XML serializer.

I am tapping into a 3rd party DLL which means I cannot alter the class City nor the field Location which requires special conversion. That is problem #1.

The second problem is to handle the reference looping within the class. To handle this I am using XmlAttributeOverrides as shown below.

The issues I am facing and would like some help on:

  • Reference looping: Ignoring a class type is ignoring all of them and I do need some fields downline in some sub-classes. How do I get more control in the over-ride?
  • Custom field conversion: How do I Intercept serialization of fields of type NetTopologySuite.Geometries.Point? and implement my own serialization like in the JSON process?

Over-write XML defaults approach:

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);

This class is way more complex so this is just a sample of what it contains:

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 conversion using Json.NET

Newtonsoft.Json.Formatting fmt = Newtonsoft.Json.Formatting.Indented; // pretty print
JsonSerializerSettings sett = new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,                // ignore Self referencing loop
    Converters = new List<JsonConverter> { new PointNsJsonConverter() }, // custom NTS converter
};
string jsonCont = JsonConvert.SerializeObject(cities, fmt, sett);

                    
public class PointNsJsonConverter : Newtonsoft.Json.JsonConverter<NetTopologySuite.Geometries.Point>
{
    public override NtsGeom.Point? ReadJson(JsonReader reader, Type objectType, NtsGeom.Point? pnt, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        // ex: SRID=4326;POINT Z(100 45 0)
        // aab.Location = new(0, 0, 0) { SRID = 4326 };

        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);
        MatchCollection ms = MyRegex.Matches(rdrVal);

        if (ms.Count == 1)
        {
            int srid = Convert.ToInt32(ms[0].Groups["srid"].Value);
            double x = Convert.ToDouble(ms[0].Groups["x"].Value);
            double y = Convert.ToDouble(ms[0].Groups["y"].Value);
            double z = Convert.ToDouble(ms[0].Groups["z"].Value);
            return new(x, y, z) { SRID = srid };
        }
        else
        {
            return null;
        }
    }

    public override void WriteJson(JsonWriter writer, NtsGeom.Point? pnt, Newtonsoft.Json.JsonSerializer serializer)
    {
        if (pnt is null) return;

        // 
        // 
        // ex: SRID=4326;POINT Z(100 45 0)
        string ewkt = string.Format("SRID=4326;{0}", pnt.ToString());
        writer.WriteValue(ewkt);
    }

    //=====================================================================================================================================
    // By overriding CanWrite property as true, we make the converter eligible for serialization. In contrast, the false CanRead property
    // instructs the framework to ignore this converter in deserialization. That’s why, even though we have to add ReadJson method (because
    // it is abstract), this will never be called on deserialization.
    //=====================================================================================================================================

    public override bool CanRead => true;

    public override bool CanWrite => true;
}

                

I am converting a class to JSON and XML. The Json conversion for the class cities is done and is shown below. I am trying to do the same with an XML output, but see that these options for field custom conversion and loop handling are not native to the XML serializer.

I am tapping into a 3rd party DLL which means I cannot alter the class City nor the field Location which requires special conversion. That is problem #1.

The second problem is to handle the reference looping within the class. To handle this I am using XmlAttributeOverrides as shown below.

The issues I am facing and would like some help on:

  • Reference looping: Ignoring a class type is ignoring all of them and I do need some fields downline in some sub-classes. How do I get more control in the over-ride?
  • Custom field conversion: How do I Intercept serialization of fields of type NetTopologySuite.Geometries.Point? and implement my own serialization like in the JSON process?

Over-write XML defaults approach:

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);

This class is way more complex so this is just a sample of what it contains:

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 conversion using Json.NET

Newtonsoft.Json.Formatting fmt = Newtonsoft.Json.Formatting.Indented; // pretty print
JsonSerializerSettings sett = new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,                // ignore Self referencing loop
    Converters = new List<JsonConverter> { new PointNsJsonConverter() }, // custom NTS converter
};
string jsonCont = JsonConvert.SerializeObject(cities, fmt, sett);

                    
public class PointNsJsonConverter : Newtonsoft.Json.JsonConverter<NetTopologySuite.Geometries.Point>
{
    public override NtsGeom.Point? ReadJson(JsonReader reader, Type objectType, NtsGeom.Point? pnt, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        // ex: SRID=4326;POINT Z(100 45 0)
        // aab.Location = new(0, 0, 0) { SRID = 4326 };

        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);
        MatchCollection ms = MyRegex.Matches(rdrVal);

        if (ms.Count == 1)
        {
            int srid = Convert.ToInt32(ms[0].Groups["srid"].Value);
            double x = Convert.ToDouble(ms[0].Groups["x"].Value);
            double y = Convert.ToDouble(ms[0].Groups["y"].Value);
            double z = Convert.ToDouble(ms[0].Groups["z"].Value);
            return new(x, y, z) { SRID = srid };
        }
        else
        {
            return null;
        }
    }

    public override void WriteJson(JsonWriter writer, NtsGeom.Point? pnt, Newtonsoft.Json.JsonSerializer serializer)
    {
        if (pnt is null) return;

        // https://en.wikipedia./wiki/Well-known_text_representation_of_geometry
        // https://en.wikipedia./wiki/PostGIS
        // ex: SRID=4326;POINT Z(100 45 0)
        string ewkt = string.Format("SRID=4326;{0}", pnt.ToString());
        writer.WriteValue(ewkt);
    }

    //=====================================================================================================================================
    // By overriding CanWrite property as true, we make the converter eligible for serialization. In contrast, the false CanRead property
    // instructs the framework to ignore this converter in deserialization. That’s why, even though we have to add ReadJson method (because
    // it is abstract), this will never be called on deserialization.
    //=====================================================================================================================================

    public override bool CanRead => true;

    public override bool CanWrite => true;
}

                
Share Improve this question asked Mar 29 at 19:05 sinDizzysinDizzy 1,3647 gold badges34 silver badges65 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

I faced similar issue with Reference Loop Handling. And here is how I tackled it:

since XmlSerializer doesn't support ReferenceLoopHandling. Your use of XmlAttributeOverrides is correct for excluding fields at the type level, but it doesn’t give fine-grained control over nested loops.

What I did differently to solve this: Map your original object to a simplified DTO before serialization. This lets you flatten or exclude references manually. You can use AutoMapper or write your own projection logic to strip the loops.

And for the Custom Field Conversion for NetTopologySuite.Geometries.Point

Since XmlSerializer doesn’t support custom converters like Json.NET, and you cannot modify the City class, the best approach is to use a shadow property as shown below:

using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using NetTopologySuite.Geometries;

public class City
{
    public int CityId { get; set; }
    public string CityCode { get; set; }

    [XmlIgnore]
    public Point Location { get; set; }

    [XmlElement("Location")]
    public string XmlLocation
    {
        get => Location != null ? $"SRID={Location.SRID};{Location}" : null;
        set
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                var match = Regex.Match(value, @"SRID=(\d+);POINT Z\(([-\d.]+) ([-\d.]+) ([-\d.]+)\)");
                if (match.Success)
                {
                    Location = new Point(
                        Convert.ToDouble(match.Groups[2].Value),
                        Convert.ToDouble(match.Groups[3].Value),
                        Convert.ToDouble(match.Groups[4].Value))
                    {
                        SRID = int.Parse(match.Groups[1].Value)
                    };
                }
            }
        }
    }
}
var city = new City
{
    CityId = 1,
    CityCode = "NYC",
    Location = new Point(100, 45, 0) { SRID = 4326 }
};

// Serialize to XML
var serializer = new XmlSerializer(typeof(City));
using (var writer = new StringWriter())
{
    serializer.Serialize(writer, city);
    Console.WriteLine("XML Output:\n" + writer.ToString());
}

string xml = @"<City>
    <CityId>1</CityId>
    <CityCode>NYC</CityCode>
    <Location>SRID=4326;POINT Z(100 45 0)</Location>
</City>";

using (var reader = new StringReader(xml))
{
    var deserialized = (City)serializer.Deserialize(reader);
    Console.WriteLine($"Deserialized Point: {deserialized.Location}");
}

This helps you serialize the Loation field as a string, while keeping the actual point logic intact

发布评论

评论列表(0)

  1. 暂无评论