7

データをシリアル化するときに車輪の再発明を避けたい。相互にリンクされているオブジェクトをシリアル化する方法をいくつか知っていますが、シリアル化のためにいくつかのコードを記述することから大量のコードを記述することまで、さまざまであり、それを避けたいと思います。いくつかの一般的な解決策があるはずです。

次のような構造があるとしましょう。

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 などにすることもできます。

要件:

  1. フォーマットは人間が読める必要があり、できれば人間が書き込める必要もあります。したがって、優先順に、JSON、YAML*、XML、カスタムです。バイナリ形式はありません。

  2. シリアル化は、.NET が提供するすべての優れた機能をサポートする必要があります。IEnumerable<>、IDictionary<> などの型を含むジェネリックは必須です。動的型/型指定されていないオブジェクトが望ましいです。

  3. フォーマットは実行可能であってはなりません。Lua、Python などのスクリプトなどはありません。

  4. 一意の ID が生成される場合、ファイルはバージョン管理システムに配置されるため、ID は安定している (シリアライゼーションとデシリアライゼーションによって持続する) 必要があります。

* YAML について聞いたことがありますが、残念ながら、ほとんど死んでいるようです。

4

1 に答える 1

12

JSON.NET (素晴らしいライブラリ!) を使用して問題を解決しました。これで、オブジェクトは最初にシリアル化され、必要な場所で正確に参照されます。第二に、多数の「$id」および「$ref」フィールドがありません。私のソリューションでは、オブジェクトの最初のプロパティがその識別子として使用されます。

2 つの を作成しましたJsonConvertor(オブジェクトへの参照用と参照されるオブジェクト用):

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));
    }
}

および参照データを保持するコンテキスト (各タイプの辞書を使用するため、ID は同じタイプのオブジェクト間でのみ一意である必要があります):

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()),
});

私はこのきれいなJSONを取得します:

{
  "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
      }
    }
  ]
}

私のソリューションで気に入らないのはJObject、技術的には不要ですが、 を使用する必要があることです。おそらくかなりの数のオブジェクトが作成されるため、読み込みが遅くなります。しかし、これは、オブジェクトのコンバーターをカスタマイズするために最も広く使用されているアプローチのようです。これを回避するために使用できるメソッドは、とにかく非公開です。

于 2012-10-28T22:59:40.050 に答える