我可以强制System.Text.Json.JsonSerializer以非递归方式工作吗?

eblbsuwk  于 5个月前  发布在  其他
关注(0)|答案(1)|浏览(47)

我基本上有一个类似于图的结构。像这样:

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.”。
有没有办法让这一切顺利进行?

x3naxklr

x3naxklr1#

没有办法做到你想要的。System.Text.Json是一个单通道序列化器,所以它不能向前看**来解析引用。
作为一种解决方法,您可以禁用Node.neighbors的直接序列化,并将Whole替换为DTO,该DTOallPossibleNodes列表和邻居表序列化为两个单独的顺序属性。这样做将把递归深度限制在可管理的范围内。
为此,请按如下所示修改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列表中序列化,并且该列表实际上包含所有可到达的节点。

如果这些假设不正确,用[JsonIgnore]标记neighbors可能会导致问题。

  • 如果文件大小有问题,请确保设置WriteIndented = false而不是true
  • 不再需要设置options.MaxDepth = Int.MaxValue。节点将不会被递归序列化,因此序列化深度将被限制。

演示小提琴here

相关问题