【问题标题】:Json.Net custom serialization from attribute来自属性的 Json.Net 自定义序列化
【发布时间】:2013-10-03 16:09:57
【问题描述】:

我已经很好地阅读了 json.net 上的文档,但我的想法已经不多了。我有一种情况,我有孩子提到那里的父母。所以

public class ObjA
{
  public int Id {get;set}
  public string OtherStuff {get;set}
  public ObjB MyChild {get;set}
}

public class ObjB
{
  public int Id {get;set}
  public string OtherStuff {get;set}
  public ObjA MyParent {get;set}
}

这不会序列化。所以我可以做 [JsonIgnore] 但我宁愿做 [JsonIdOnly] 其中 ObjB,而不是对 MyParent 进行序列化或完全跳过 MyParent 显示 MyParentId:123 的 json 属性。所以

{OjbA:{
    Id:123,
    OtherStuff:some other stuff,
    MyChild:{
        Id:456,
        OtherStuff:Some other stuff,
        MyParentId:123,
        }
    }
 }

我知道我可以为一个类型编写一个自定义转换器。问题是我希望这仅在指定时发生,否则我将无法序列化 ObjA。换句话说,我只需要在我用属性装饰它时才会发生这种情况。所以

public class ObjB
{
  public int Id {get;set}
  public string OtherStuff {get;set}
  [JsonIdOnly]
  public ObjA MyParent {get;set}
}

【问题讨论】:

    标签: c# json serialization attributes json.net


    【解决方案1】:

    如果您不关心 JSON 中是否包含一些额外的簿记信息,请按照 @Athari 的建议将 JsonSerializerSettings 中的 PreserveReferenceHandling 选项设置为 All(或 Objects)。这是使其工作的最简单方法。如果这样做,您的 JSON 将如下所示:

    {
      "$id": "1",
      "Id": 123,
      "OtherStuff": "other stuff A",
      "MyChild": {
        "$id": "2",
        "Id": 456,
        "OtherStuff": "other stuff B",
        "MyParent": {
          "$ref": "1"
        }
      }
    }
    

    也就是说,有一种方法可以完成您最初想要的操作,使用自定义 JsonConverter。您可以做的是制作一个转换器,该转换器将接受任何具有 Id 属性的对象。然后,对于那些只希望将其序列化为 Id 的地方,您可以使用 [JsonConverter] 属性装饰这些属性。然后自定义转换器将用于这些情况,但不是其他情况。以下是转换器的外观:

    class IdOnlyConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType.IsClass;
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteStartObject();
            writer.WritePropertyName("Id");
            writer.WriteValue(GetId(value));
            writer.WriteEndObject();
        }
    
        private int GetId(object obj)
        {
            PropertyInfo prop = obj.GetType().GetProperty("Id", typeof(int));
            if (prop != null && prop.CanRead)
            {
                return (int)prop.GetValue(obj, null);
            }
            return 0;
        }
    
        public override bool CanRead 
        { 
            get { return false; } 
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    要使用转换器,您需要按照概述的方式设置类。请注意 MyParent 是如何用一个属性装饰的,以告诉 Json.Net 使用该属性的自定义转换器。

    public class ObjA
    {
        public int Id { get; set; }
        public string OtherStuff { get; set; }
        public ObjB MyChild { get; set; }
    }
    
    public class ObjB
    {
        public int Id { get; set; }
        public string OtherStuff { get; set; }
        [JsonConverter(typeof(IdOnlyConverter))]
        public ObjA MyParent { get; set; }
    }
    

    序列化时,您需要将JsonSerializerSettingsReferenceLoopHandling 选项设置为Serialize,以告诉Json.Net 如果检测到引用循环不要抛出错误,并继续序列化(因为我们的转换器会处理的)。

    总而言之,这里有一些示例代码演示了转换器的实际应用:

    class Program
    {
        static void Main(string[] args)
        {
            ObjA a = new ObjA();
            a.Id = 123;
            a.OtherStuff = "other stuff A";
    
            ObjB b = new ObjB();
            b.Id = 456;
            b.OtherStuff = "other stuff B";
            b.MyParent = a;
    
            a.MyChild = b;
    
            JsonSerializerSettings settings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize,
                Formatting = Newtonsoft.Json.Formatting.Indented
            };
    
            string json = JsonConvert.SerializeObject(a, settings);
            Console.WriteLine(json);
        }
    }
    

    这是上面的输出:

    {
      "Id": 123,
      "OtherStuff": "other stuff A",
      "MyChild": {
        "Id": 456,
        "OtherStuff": "other stuff B",
        "MyParent": {
          "Id": 123
        }
      }
    }
    

    【讨论】:

    • 哦,好吧,是的,我创建了一个转换器,但不知道如何让他进入一个属性。这将正常工作。谢谢你们。
    • 嗨,一切都很好,除了我收到此错误 { "Message": "An error has occurred.", "ExceptionMessage": "The 'ObjectContent`1' type failed to serialize the内容类型'application/json; charset=utf-8'的响应正文。”,“ExceptionType”:“System.InvalidOperationException”,“StackTrace”:null,“InnerException”:{“Message”:“发生错误。 ", "ExceptionMessage": "创建 GuidEntityToIdJsonConverter 时出错", "ExceptionType": "Newtonsoft.Json.JsonException",
    • 好吧,好吧,所以在那个堆栈跟踪的末尾有一个内部错误说“没有无参数构造函数”,这通常是“你永远找不到错误”的代码,但这次我只是创建了一个无参数的构造函数并且它起作用了。那怎么样?
    • 啊。是的,我只是在写异常是说 Json.Net 出于某种原因无法实例化您的GuidEntityToIdJsonConverter。一个可能的原因是它没有无参数构造函数。 (使用 [JsonConverter] 属性中的转换器时需要无参数构造函数,因为 Json.net 不知道传递什么给其他构造函数以实例化转换器。)
    • IdOnlyConverter 在尝试反序列化装饰字段时给了我一些问题。在我覆盖 CanRead 方法后它起作用了:public override bool CanRead { get { return false; } }
    【解决方案2】:

    您可以将JsonSerializerSettings.PreserveReferencesHandling 更改为PreserveReferencesHandling.ObjectsPreserveReferencesHandling.All(您可能还需要更改JsonSerializerSettings.ReferenceLoopHandling 和有关引用的各种JsonPropertyAttribute 属性)。这将向对象添加$ref JSON 属性,并保留交叉链接。

    您可以使用自定义IReferenceResolver 修改$ref 属性的文本。

    但是,如果您需要更复杂的引用解析,就像您建议的那样,没有$ref 属性,那么您将不得不编写一些代码。

    【讨论】:

    • 感谢您的回复。我没有看到这些属性,正在调查。但是,我很乐意编写一些代码来获得我想要的东西。我只是无法从customConverter 和converterAttribute 中弄清楚。我现在正在阅读上述道具(实际上我要去吃午饭,但是那时),目的是弄清楚如何编写我自己的扩展。如果您碰巧知道正确的方向,那么很高兴知道。再次感谢,raif
    • 在我去吃午饭之前真的很快,看起来 ReferenceLoopHandling 非常接近。如果我可以通过某种完美的机制继承或扩展它。
    • 如果您的 JSON 看起来并不重要,我建议坚持使用 $ref 属性。我已经在my private framework 中实现了自定义引用,但恐怕它太复杂而不能成为一个好的示例。它使您在上面建议的链接成为可能,但它需要大量属性和一些自定义代码;并且没有文档。 :)
    • @Raif 差点忘了!你可以使用这个:stackoverflow.com/a/13113901/293099 这是我上面链接的类的第一个版本。对象具有字符串标识符,每种类型都是唯一的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 2013-09-02
    • 1970-01-01
    • 2015-07-27
    • 1970-01-01
    • 2017-06-30
    相关资源
    最近更新 更多