System.Text.Json如何将含循环引用的ImageSource属性序列化为字符串
System.Text.Json序列化IUserProduct时循环引用异常的解决
问题场景
使用System.Text.Json序列化IUserProduct接口实例列表时,因ImageSource属性存在自引用,抛出System.Text.Json.JsonException: 'A possible object cycle was detected.'异常。接口定义如下:
public interface IUserProduct { public int Gramature { get; set; } public ImageSource ProductImage { get; } public string Name { get; } public decimal Kcal { get; } public decimal Carbohydrates { get; } public decimal Fat { get; } public decimal Proteins { get; } public IProductDataTemplate ProductTemplate { get; set; } }
现有尝试
- 编写了针对
IList<IUserProduct>的自定义转换器,试图将ImageSource转为字符串序列化,但仍抛出异常:
public override void Write(Utf8JsonWriter writer, IList<IUserProduct> value, JsonSerializerOptions options) { writer.WriteStartArray(); foreach (IUserProduct product in value) { writer.WriteStartObject(); writer.WriteString(nameof(product.Name), product.Name); writer.WriteNumber(nameof(product.Kcal), product.Kcal); writer.WriteNumber(nameof(product.Gramature), product.Gramature); writer.WriteNumber(nameof(product.Proteins), product.Proteins); writer.WriteNumber(nameof(product.Carbohydrates), product.Carbohydrates); writer.WriteNumber(nameof(product.Fat), product.Fat); string image = product.ProductImage.ToString(); writer.WriteString(nameof(product.ProductImage), image); writer.WriteEndObject(); } writer.WriteEndArray(); }
- 尝试在
JsonSerializerOptions中添加ReferenceHandler.Preserve和ReferenceHandler.IgnoreCycles,均无效。 - 序列化方法代码:
private static readonly JsonSerializerOptions ProductSerializer = new JsonSerializerOptions() { Converters = { new JsonProductCustomConverter() }//自定义转换器 }; private static void WriteProductsJSON(IUserProduct product, string fileName) { string json; List<IUserProduct> products = new List<IUserProduct>(); if (File.Exists(Path.Combine(DataPath, fileName))) { json = File.ReadAllText(Path.Combine(DataPath, fileName)); products = System.Text.Json.JsonSerializer.Deserialize<List<IUserProduct>>(json, options: ProductSerializer); } products.Add(product); json = System.Text.Json.JsonSerializer.Serialize(products, options: ProductSerializer); File.WriteAllText(Path.Combine(DataPath, fileName), json); }
错误原因分析
- 自定义转换器遗漏关键属性:接口中的
ProductTemplate属性未在转换器中处理,System.Text.Json会尝试默认序列化该属性,若IProductDataTemplate内部存在循环引用,仍会触发异常。 - ImageSource的ToString()未彻底解决循环:部分
ImageSource实现的ToString()可能仍包含循环引用的对象引用,而非纯字符串标识(如Uri、文件路径)。 - 转换器继承逻辑可能有误:若自定义转换器未正确继承
JsonConverter<IList<IUserProduct>>,可能导致序列化时未被调用,仍使用默认逻辑处理对象。
解决方案
方案1:完善自定义转换器,显式处理所有属性
修改转换器,覆盖所有属性的序列化逻辑,避免默认序列化触发循环:
public class JsonProductCustomConverter : JsonConverter<IList<IUserProduct>> { public override IList<IUserProduct> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var list = new List<IUserProduct>(); using (JsonDocument doc = JsonDocument.ParseValue(ref reader)) { foreach (var element in doc.RootElement.EnumerateArray()) { // 假设UserProduct是IUserProduct的实现类,需根据实际情况调整 var product = new UserProduct { Name = element.GetProperty(nameof(IUserProduct.Name)).GetString(), Kcal = element.GetProperty(nameof(IUserProduct.Kcal)).GetDecimal(), Gramature = element.GetProperty(nameof(IUserProduct.Gramature)).GetInt32(), Proteins = element.GetProperty(nameof(IUserProduct.Proteins)).GetDecimal(), Carbohydrates = element.GetProperty(nameof(IUserProduct.Carbohydrates)).GetDecimal(), Fat = element.GetProperty(nameof(IUserProduct.Fat)).GetDecimal(), // 从序列化的字符串重建ImageSource ProductImage = ImageSource.FromUri(new Uri(element.GetProperty(nameof(IUserProduct.ProductImage)).GetString())), // 若不需要序列化ProductTemplate,可跳过赋值;若需要需自定义解析逻辑 // ProductTemplate = ... }; list.Add(product); } } return list; } public override void Write(Utf8JsonWriter writer, IList<IUserProduct> value, JsonSerializerOptions options) { writer.WriteStartArray(); foreach (IUserProduct product in value) { writer.WriteStartObject(); writer.WriteString(nameof(product.Name), product.Name); writer.WriteNumber(nameof(product.Kcal), product.Kcal); writer.WriteNumber(nameof(product.Gramature), product.Gramature); writer.WriteNumber(nameof(product.Proteins), product.Proteins); writer.WriteNumber(nameof(product.Carbohydrates), product.Carbohydrates); writer.WriteNumber(nameof(product.Fat), product.Fat); // 提取ImageSource的纯字符串标识,避免循环引用 string imageStr = string.Empty; if (product.ProductImage is UriImageSource uriImg) { imageStr = uriImg.Uri.ToString(); } else if (product.ProductImage is FileImageSource fileImg) { imageStr = fileImg.File; } writer.WriteString(nameof(product.ProductImage), imageStr); // 若不需要序列化ProductTemplate,直接跳过;若需要需确保其无循环引用 // writer.WritePropertyName(nameof(product.ProductTemplate)); // JsonSerializer.Serialize(writer, product.ProductTemplate, options); writer.WriteEndObject(); } writer.WriteEndArray(); } }
方案2:为ImageSource单独编写自定义转换器
针对ImageSource类型编写专属转换器,从根源解决其循环问题,同时保留默认逻辑处理其他属性:
public class ImageSourceConverter : JsonConverter<ImageSource> { public override ImageSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string str = reader.GetString(); if (Uri.TryCreate(str, UriKind.Absolute, out Uri uri)) { return ImageSource.FromUri(uri); } return ImageSource.FromFile(str); } public override void Write(Utf8JsonWriter writer, ImageSource value, JsonSerializerOptions options) { string str = string.Empty; if (value is UriImageSource uriImg) { str = uriImg.Uri.ToString(); } else if (value is FileImageSource fileImg) { str = fileImg.File; } // 其他ImageSource类型可按需扩展 writer.WriteStringValue(str); } }
修改序列化配置:
private static readonly JsonSerializerOptions ProductSerializer = new JsonSerializerOptions() { Converters = { new ImageSourceConverter() }, ReferenceHandler = ReferenceHandler.IgnoreCycles // 额外保障,避免其他属性的循环引用 };
方案3:忽略无需序列化的属性
若ProductTemplate不需要序列化,可在接口的实现类中添加[JsonIgnore]特性:
public class UserProduct : IUserProduct { // 其他属性实现... [JsonIgnore] public IProductDataTemplate ProductTemplate { get; set; } }
内容的提问来源于stack exchange,提问作者SEGMK
相关产品推荐
相关产品推荐

