【问题标题】:Deserialize JSON to type containing reference to another deserialized object in C#将 JSON 反序列化为包含对 C# 中另一个反序列化对象的引用的类型
【发布时间】:2025-12-17 03:10:01
【问题描述】:

我有以下型号:

public interface IEntity
{
    string Id { get; }
}

public class EntityParent : IEntity
{
    public string Id { get; }

    public EntityChild EntityChild { get; }

    [JsonConstructor]
    public EntityParent(string id, EntityChild entityChild)
    {
        Id = id;
        EntityChild = entityChild;
    }
}

public class EntityChild : IEntity
{
    public string Id { get; }

    public int Age { get; }

    [JsonConstructor]
    public EntityChild(string id, int age)
    {
        Id = id;
        Age = age;
    }
}

接下来我需要将一些 JSON 反序列化为上述类型的集合:

{
            "Children":
            [
                {
                    "Id"          : "Billy",
                    "Age"         : 42
                }
            ],

            "Parents" :
            [
                {
                    "Id"          : "William",
                    "EntityChild" : "Billy"
                }
            ]
}

最终我想要一个EntityChildren 列表和一个EntityParents 列表,它们将(可选地)包含对第一个列表中对象的引用,或者至少包含对EntityChild 实例的引用。我试图编写一个自定义JsonConverter(我正在使用Newtonsoft.Json 9.0.1 NuGet 包),在ReadJson() 方法中我正在寻找一个具有特定Id 的孩子,像这样:

public class ParentConverter<TEntity> : JsonConverter where TEntity : IEntity
{
    private readonly IEnumerable<TEntity> _children;

    public ParentConverter(IEnumerable<TEntity> children)
    {
        _children = children;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        JObject jObject = JObject.Load(reader);

        TEntity target = _children.FirstOrDefault(d => d.Id == jObject["Id"].ToString());

        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(TEntity).IsAssignableFrom(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

这是一个简单的测试:

public class JsonTest
{
    const string Json = @"
        {
            ""Children"": [
                {
                    ""Id""          : ""Billy"",
                    ""Age""         : 42
                }
            ],

            ""Parents"" : [
                {
                    ""Id""          : ""William"",
                    ""EntityChild"" : ""Billy""
                }
            ]
        }";

    public static void Main()
    {
        JObject jObject = JObject.Parse(Json);
        var children =
            JsonConvert.DeserializeObject<List<EntityChild>>(jObject["Children"].ToString());
        var parents =
            JsonConvert.DeserializeObject<List<EntityParent>>(jObject["Parents"].ToString(),
                new ParentConverter<EntityChild>(children));
    }
}

children 已正确反序列化,但 parents 在尝试在 ReadJson() 中调用 JObject.Load(reader); 时抛出 JsonReaderException,说“从 JsonReader 读取 JObject 时出错。当前 JsonReader 项不是对象:字符串。路径'[0].EntityChild'。"

有人知道我应该怎么做吗?提前致谢。

编辑:用额外的属性更新了EntityChild,以强调EntityParent 上的属性必须是EntityChild 类型,并且不是字符串

【问题讨论】:

  • 这个 JSON 结构是谁设计的?这很奇怪
  • @SirRufo 不幸的是它不在我的手中:(,而且不是我;)。

标签: c# json deserialization


【解决方案1】:

实体父级应如下:

public class EntityParent : IEntity
{
    public string Id { get; }

    public string EntityChild { get; }

    [JsonConstructor]
    public EntityParent(string id, string entityChild)
    {
        Id = id;
        EntityChild = entityChild;
    }
}

main() 中更改如下:

var parents = JsonConvert.DeserializeObject<List<EntityParent>>(jObject["Parents"].ToString());

它有效。

【讨论】:

  • 是的,但关键是要有一个EntityChild类型的属性,所以在模型中我可以使用自定义类型而不是字符串。该示例故意简单,但在实际场景中,我将在 EntityChild 上拥有除 Id 之外的其他属性,然后这个解决方案就不够了。
【解决方案2】:

以下是使用 LINQ 的一种解决方法,我基本上将父 JObject 与子 JObject 连接起来以构建父和子:

    public static void Main()
    {
        JObject jObject = JObject.Parse(Json);

        IEnumerable<EntityParent> parents =
            from parent in jObject["Parents"]
            join child in jObject["Children"] on parent["EntityChild"] equals child["Id"]
            select
                new EntityParent(
                    parent["Id"].ToString(),
                    new EntityChild(
                        child["Id"].ToString(),
                        child["Age"].ToObject<int>()));
    }

如果子列表已经存在,则可以在该列表上执行连接,如下所示:

    public static void Main()
    {
        JObject jObject = JObject.Parse(Json);

        var children =
            JsonConvert.DeserializeObject<List<EntityChild>>(jObject["Children"].ToString());

        IEnumerable<EntityParent> parents =
            from parent in jObject["Parents"]
            join child in children on parent["EntityChild"] equals child.Id
            select new EntityParent(parent["Id"].ToString(), child);
    }

【讨论】: