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
1 Answer
Reset to default 1I 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