我基本上有一个类似图表的 struct .大概是这样的:

public class Whole {
    public List<Node> allPossibleNodes = new List<Node>();
}

public class Node {
    public List<Node> neighbors = new List<Node>();
}

现在,我想使用以下代码将其序列化:

public class WriteTest {
    static JsonSerializerOptions options = new () {
        WriteIndented = true,
        IncludeFields = true,
        IgnoreReadOnlyProperties = true,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        ReferenceHandler = ReferenceHandler.Preserve,
    };

    public static void write() {
        var w = new Whole();
        Node? p = null; //previous
        for (int i = 0; i < 3; i++) {
            var n = new Node();
            w.allPossibleNodes.Add(n);
            if (p != null) {
                n.neighbors.Add(p);
                p.neighbors.Add(n);
            }
            p = n;
        }


        var json = JsonSerializer.Serialize(w, options);
        File.WriteAllText("test.json", json);
    }

    public static void read() {
        var json = File.ReadAllText("test2.json");
        var w = JsonSerializer.Deserialize<Whole>(json, options);
    }
}

它会产生以下输出:

{
  "$id": "1",
  "allPossibleNodes": {
    "$id": "2",
    "$values": [
      {
        "$id": "3",
        "neighbors": {
          "$id": "4",
          "$values": [
            {
              "$id": "5",
              "neighbors": {
                "$id": "6",
                "$values": [
                  {
                    "$ref": "3"
                  },
                  {
                    "$id": "7",
                    "neighbors": {
                      "$id": "8",
                      "$values": [
                        {
                          "$ref": "5"
                        }
                      ]
                    }
                  }
                ]
              }
            }
          ]
        }
      },
      {
        "$ref": "5"
      },
      {
        "$ref": "7"
      }
    ]
  }
}

它的问题在于:

  1. 它最终会抛给我一个例外:

"JsonException:‘检测到可能的对象循环.这可能是 由于周期或如果对象深度大于最大值 允许的深度为64."

或者,如果我设置为options.MaxDepth = Int.MaxValue;,它可能会使堆栈溢出.

  1. 我不只是丑陋的,人类不可读的垃圾.

我想要的是这样的东西:

{
  "$id": "1",
  "allPossibleNodes": {
    "$id": "2",
    "$values": [
      {
        "$id": "3",
        "neighbors": {
          "$id": "6",
          "$values": [
            {
                "$ref": "4"
            }
          ]
        }
      },
      {
        "$id": "4",
        "neighbors": {
          "$id": "7",
          "$values": [
            {
                "$ref": "3"
            },
            {
                "$ref": "5"
            },
          ]
        }
      },
      {
        "$id": "5",
        "neighbors": {
          "$id": "8",
          "$values": [
            {
                "$ref": "4"
            }
          ]
        }
      }
    ]
  }
}

但不幸的是,序列化程序甚至无法读取它,只能哭着说"'Reference'4' was not found."

有没有办法让这一切顺利进行?

推荐答案

没有办法完全按照你的意愿go 做.System.Text.Json是single pass serializer,因此它不能看起来像forward来解析引用.

作为一种解决方法,您可以禁用Node.neighbors的直接序列化,并将Whole替换为DTO,将allPossibleNodes列表和邻居表序列化为两个单独的顺序属性. 这样做会将递归深度限制在可管理的范围内.

为此,请按如下方式修改WholeNode,为Whole引入一个自定义转换器:

[JsonConverter(typeof(WholeConverter))]
public class Whole {
    public List<Node> allPossibleNodes = new List<Node>();
}

public class Node {
    [JsonIgnore]
    public List<Node> neighbors = new List<Node>();
}

class WholeConverter : JsonConverter<Whole>
{
    struct WholeDto
    {
        [JsonPropertyOrder(1)]
        public List<Node>? allPossibleNodes { get; set; }
        [JsonPropertyOrder(2)]
        public IEnumerable<NeighborItem>? neighborTable { get; set; }
    }
    
    record struct NeighborItem(Node node, List<Node> neighbors);

    public override Whole? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var dto = JsonSerializer.Deserialize<WholeDto>(ref reader, options);
        foreach (var item in dto.neighborTable ?? Enumerable.Empty<NeighborItem>().Where(i => i.node != null))
            item.node.neighbors = item.neighbors;
        var whole = new Whole();
        if (dto.allPossibleNodes != null)
            whole.allPossibleNodes = dto.allPossibleNodes;
        return whole;
    }

    public override void Write(Utf8JsonWriter writer, Whole value, JsonSerializerOptions options) =>
        JsonSerializer.Serialize(writer,
            new WholeDto 
            { 
                allPossibleNodes = value.allPossibleNodes,
                neighborTable = value.allPossibleNodes.Where(n => n.neighbors.Count > 0).Select(n => new NeighborItem(n, n.neighbors)),
            },
            options);
}

生成的JSON将如下所示:

{
  "allPossibleNodes": {
    "$id": "1",
    "$values": [
      {
        "$id": "2"
      },
      {
        "$id": "3"
      },
      {
        "$id": "4"
      }
    ]
  },
  "neighborTable": {
    "$id": "5",
    "$values": [
      {
        "node": {
          "$ref": "2"
        },
        "neighbors": {
          "$id": "6",
          "$values": [
            {
              "$ref": "3"
            }
          ]
        }
      },
      {
        "node": {
          "$ref": "3"
        },
        "neighbors": {
          "$id": "7",
          "$values": [
            {
              "$ref": "2"
            },
            {
              "$ref": "4"
            }
          ]
        }
      },
      {
        "node": {
          "$ref": "4"
        },
        "neighbors": {
          "$id": "8",
          "$values": [
            {
              "$ref": "3"
            }
          ]
        }
      }
    ]
  }
}

它不像您想要的JSON那样干净,但仍然是可管理的.

备注:

  • 该代码假设Node将只与Whole.allPossibleNodes列表一起序列化,并且该列表实际上包含所有可到达的 node .

    如果这些假设不正确,则将neighbors标记为[JsonIgnore]可能会导致问题.

  • 如果文件大小是一个问题,请确保设置为WriteIndented = false而不是true.

  • 不再需要设置options.MaxDepth = Int.MaxValue. node 不会被递归序列化,因此序列化深度将受到限制.

演示小提琴here.

Csharp相关问答推荐

FromServices不使用WebAppliationFactory程序>

EF Core. Income和. AsNoTracking正确用法

读取配置文件(mytest. exe. config)

实体核心框架--HasColumnType和HasPrecision有什么不同?

获取具有AutoFaces的所有IOptions对象的集合

使用预定义对象减少Task.Run/Factory.StartNew中的关闭开销

如何将此方法参数化并使其更灵活?

带有可选参数的模拟方法返回意外的不同值,具体取决于可选的默认值

什么类型的对象存储在大对象堆(LOH)中

RabbitMQ群集的MassTransit配置问题

是否可以在Entity Framework Core中使用只读 struct 作为拥有实体?

如何从非异步任务中正确返回TypeResult

如何从Azure函数使用Graph API(SDK 5.35)中的[FindMeetingTimes]

使用Blazor WebAssembly提高初始页面加载时间的性能

我应该为C#12中的主构造函数参数创建私有属性吗?

嵌套Blazor组件内的验证

为什么C#中的类型别名不能在另一个别名中使用?

如何对列表<;列表>;使用集合表达式?

与另一个对象位于同一位置的对象具有不同的变换位置

MudRadioGroup不切换