C# 序列化.NET中高度链接的数据(自定义JSON.NET引用)

C# 序列化.NET中高度链接的数据(自定义JSON.NET引用),c#,.net,json,serialization,json.net,C#,.net,Json,Serialization,Json.net,我想避免在序列化数据时重复使用轮子。我知道一些序列化相互链接的对象的方法,但它的范围从编写一些代码到编写大量用于序列化的代码,我希望避免这种情况。必须有一些通用的解决方案 假设我有一个这样的结构: Person bro = new Person { name = "bro", pos = new Pos { x = 1, y = 5 } }, sis = new Person { name = "sis", pos = new Pos { x = 2, y = 6 } },

我想避免在序列化数据时重复使用轮子。我知道一些序列化相互链接的对象的方法,但它的范围从编写一些代码到编写大量用于序列化的代码,我希望避免这种情况。必须有一些通用的解决方案

假设我有一个这样的结构:

Person
    bro = new Person { name = "bro", pos = new Pos { x = 1, y = 5 } },
    sis = new Person { name = "sis", pos = new Pos { x = 2, y = 6 } },
    mom = new Person { name = "mom", pos = new Pos { x = 3, y = 7 }, 
        children = new List<Person> { bro, sis }
    },
    dad = new Person { name = "dad", pos = new Pos { x = 4, y = 8 }, 
        children = new List<Person> { bro, sis }, mate = mom
    };
mom.mate = dad;
Family family = new Family { persons = new List<Person> { mom, dad, bro, sis } };
family: {
    persons: [
        { name: "bro", pos: { x: 1, y: 5 } },
        { name: "sis", pos: { x: 2, y: 6 } },
        { name: "mom", pos: { x: 3, y: 7 }, mate: "dad", children: [ "bro", "sis" ] },
        { name: "dad", pos: { x: 4, y: 8 }, mate: "mom", children: [ "bro", "sis" ] },
    ]
}
在这里,链接被序列化为名称,并假设名称是唯一的。链接也可以是“family.persons.0”或生成的唯一ID或其他内容

要求:

  • 格式必须是人类可读的,最好也是人类可写的。因此,按照优先顺序:JSON、YAML*、XML、custom。没有二进制格式

  • 序列化必须支持.NET提供的所有好东西必须使用泛型,包括IEnumerable、IDictionary等类型。动态类型/非类型化对象是可取的

  • 格式不能是可执行的。没有Lua、Python等脚本之类的东西

  • 如果生成唯一的ID,它们必须是稳定的(通过序列化反序列化持久化),因为文件将被放入版本控制系统中


  • *听说过YAML,但遗憾的是,它似乎已经死了。

    使用JSON.NET(神奇的库!)解决了这个问题。现在,首先,对象被序列化并被引用到我想要的地方;第二,没有大量的“$id”和“$ref”字段。在我的解决方案中,对象的第一个属性用作其标识符

    我创建了两个
    JsonConvertor
    s(用于对象引用和被引用对象):

    以及保存引用数据的上下文(每种类型都有一个字典,因此ID只需在相同类型的对象之间是唯一的):


    在我的解决方案中,我不喜欢的是,我必须使用
    JObject
    ,即使从技术上讲这是不必要的。它可能会创建相当多的对象,因此加载速度会较慢。但这似乎是定制对象转换器最广泛使用的方法。无论如何,可以用来避免这种情况的方法都是私有的。

    是否查看了
    DataContractSerializer
    JavaScriptSerializer
    JSON.NET
    ?@Oded DataContractSerializer因堆栈溢出而失败,JavaScriptSerializer在检测到循环引用后引发异常。。。JSON.NET的网站上说,“序列化循环引用”,所以我要试试。为什么您希望序列化的数据采用非常特定的结构?为什么不使用现有的序列化框架,让它们自己做呢?(因为您希望可读,所以使用XML(我认为SOAP也是“人类可读的”))从现有的序列化支持中获取错误是发明自己的错误的一个非常糟糕的理由。在做了很多工作之后,你会再次陷入完全相同的陷阱。发布真实代码以获取帮助。@Oded好的,JSON.NET确实支持循环引用,但它总是在第一次遇到对象时扩展对象,并且无法更改此行为(至少我不知道如何更改)。这就是我使用自定义IReferenceResolver得到的结果:我对JsonRefed转换器的工作原理感到困惑——每次我试图向前传递到“serializer.Serialize(writer,value)”;“当它试图为同一对象再次调用同一个转换器时,我会收到一个循环引用错误?@命名错误的RefedConverter会像没有使用转换器一样写和读,但会记住它读取的对象的ID。RefConverter只写入id字符串,并使用RefedConverter存储的信息将字符串转换为对象。您可以尝试禁用循环引用检入序列化程序设置。如果两个转换器都正确使用(一个主要源为“refed”,其余为“ref”),则不会崩溃。是否有一种方法可以在不必在每个字段中设置属性的情况下实现这一点?比如,有没有办法在ConverterSettings中设置一些东西,然后让所有东西自动工作?@pek你可以将配置移动到任何你想修改合同解析器、上下文和转换器的地方,但是配置必须存在于某个地方,因为只有程序员才知道链接和值在哪里。如果希望序列化“自动”工作,可以使用内置的引用功能,但很明显,您将无法选择链接和值的存储位置。@Athari是的,嗯,我遇到的问题是,假设我想反序列化一个人员列表,并在设置中设置引用转换器(没有使用它的属性)。问题是,在反序列化整个person和类成员具有person引用时都将使用转换器。我只希望在第二种情况下使用转换器,而不是第一种情况。希望您理解我的意思。
    interface IJsonLinkable
    {
        string Id { get; }
    }
    
    class JsonRefConverter : JsonConverter
    {
        public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer)
        {
            writer.WriteValue(((IJsonLinkable)value).Id);
        }
    
        public override object ReadJson (JsonReader reader, Type type, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType != JsonToken.String)
                throw new Exception("Ref value must be a string.");
            return JsonLinkedContext.GetLinkedValue(serializer, type, reader.Value.ToString());
        }
    
        public override bool CanConvert (Type type)
        {
            return type.IsAssignableFrom(typeof(IJsonLinkable));
        }
    }
    
    class JsonRefedConverter : JsonConverter
    {
        public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }
    
        public override object ReadJson (JsonReader reader, Type type, object existingValue, JsonSerializer serializer)
        {
            var jo = JObject.Load(reader);
            var value = JsonLinkedContext.GetLinkedValue(serializer, type, (string)jo.PropertyValues().First());
            serializer.Populate(jo.CreateReader(), value);
            return value;
        }
    
        public override bool CanConvert (Type type)
        {
            return type.IsAssignableFrom(typeof(IJsonLinkable));
        }
    }
    
    class JsonLinkedContext
    {
        private readonly IDictionary<Type, IDictionary<string, object>> links = new Dictionary<Type, IDictionary<string, object>>();
    
        public static object GetLinkedValue (JsonSerializer serializer, Type type, string reference)
        {
            var context = (JsonLinkedContext)serializer.Context.Context;
            IDictionary<string, object> links;
            if (!context.links.TryGetValue(type, out links))
                context.links[type] = links = new Dictionary<string, object>();
            object value;
            if (!links.TryGetValue(reference, out value))
                links[reference] = value = FormatterServices.GetUninitializedObject(type);
            return value;
        }
    }
    
    [JsonObject(MemberSerialization.OptIn)]
    class Family
    {
        [JsonProperty(ItemConverterType = typeof(JsonRefedConverter))]
        public List<Person> persons;
    }
    
    [JsonObject(MemberSerialization.OptIn)]
    class Person : IJsonLinkable
    {
        [JsonProperty]
        public string name;
        [JsonProperty]
        public Pos pos;
        [JsonProperty, JsonConverter(typeof(JsonRefConverter))]
        public Person mate;
        [JsonProperty(ItemConverterType = typeof(JsonRefConverter))]
        public List<Person> children;
    
        string IJsonLinkable.Id { get { return name; } }
    }
    
    [JsonObject(MemberSerialization.OptIn)]
    class Pos
    {
        [JsonProperty]
        public int x;
        [JsonProperty]
        public int y;
    }
    
    JsonConvert.SerializeObject(family, Formatting.Indented, new JsonSerializerSettings {
        NullValueHandling = NullValueHandling.Ignore,
        Context = new StreamingContext(StreamingContextStates.All, new JsonLinkedContext()),
    });
    
    JsonConvert.DeserializeObject<Family>(File.ReadAllText(@"..\..\Data\Family.json"), new JsonSerializerSettings {
        Context = new StreamingContext(StreamingContextStates.All, new JsonLinkedContext()),
    });
    
    {
      "persons": [
        {
          "name": "mom",
          "pos": {
            "x": 3,
            "y": 7
          },
          "mate": "dad",
          "children": [
            "bro",
            "sis"
          ]
        },
        {
          "name": "dad",
          "pos": {
            "x": 4,
            "y": 8
          },
          "mate": "mom",
          "children": [
            "bro",
            "sis"
          ]
        },
        {
          "name": "bro",
          "pos": {
            "x": 1,
            "y": 5
          }
        },
        {
          "name": "sis",
          "pos": {
            "x": 2,
            "y": 6
          }
        }
      ]
    }