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

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

现有尝试

  1. 编写了针对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();
}
  1. 尝试在JsonSerializerOptions中添加ReferenceHandler.Preserve和ReferenceHandler.IgnoreCycles,均无效。
  2. 序列化方法代码:
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);
}

错误原因分析

  1. 自定义转换器遗漏关键属性:接口中的ProductTemplate属性未在转换器中处理,System.Text.Json会尝试默认序列化该属性,若IProductDataTemplate内部存在循环引用,仍会触发异常。
  2. ImageSource的ToString()未彻底解决循环:部分ImageSource实现的ToString()可能仍包含循环引用的对象引用,而非纯字符串标识(如Uri、文件路径)。
  3. 转换器继承逻辑可能有误:若自定义转换器未正确继承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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:45:07