2015-10-15 4 views
5

Wie kann ich Newtonsoft.Json einrichten, um ein Objekt unter Verwendung von Legacy-Membernamen zu deserialisieren, aber es unter Verwendung des aktuellen Membernamens zu serialisieren?JSON deserialize aus Legacy-Eigenschaftsnamen

Hier ist ein Beispielobjekt, das serialisiert und deserialisiert werden muss. Ich habe einer Eigenschaft ein Attribut gegeben, das eine Liste von Namen enthält, unter denen sie in der Vergangenheit möglicherweise serialisiert wurde.

[DataContract] 
class TestObject { 
    [LegacyDataMemberNames("alpha", "omega")] 
    [DataMember(Name = "a")] 
    public int A { get; set; } 
} 

Ich möchte serialize JSon immer Namen „a“ aber in der Lage sein, auf die eine Eigenschaft von jedem Vermächtnis Namen deserialisieren einschließlich „Alpha“ und „Omega“ sowie den aktuellen Namen, „a "

Antwort

4

Dies kann mit einem benutzerdefinierten IContractResolver, durch die Verlängerung eines der bereits bestehenden Resolvern erstellt erfolgen, zum Beispiel DefaultContractResolver:

[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false, Inherited = true)] 
public class LegacyDataMemberNamesAttribute : Attribute 
{ 
    public LegacyDataMemberNamesAttribute() : this(new string[0]) { } 

    public LegacyDataMemberNamesAttribute(params string[] names) 
    { 
     this.Names = names; 
    } 

    public string [] Names { get; set; } 
} 

public class LegacyPropertyResolver : DefaultContractResolver 
{ 
    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons. 
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm 
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm 
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance." 
    static LegacyPropertyResolver instance; 

    static LegacyPropertyResolver() { instance = new LegacyPropertyResolver(); } 

    public static LegacyPropertyResolver Instance { get { return instance; } } 

    protected LegacyPropertyResolver() : base() { } 

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) 
    { 
     var properties = base.CreateProperties(type, memberSerialization); 

     for (int i = 0, n = properties.Count; i < n; i++) 
     { 
      var property = properties[i]; 
      if (!property.Writable) 
       continue; 
      var attrs = property.AttributeProvider.GetAttributes(typeof(LegacyDataMemberNamesAttribute), true); 
      if (attrs == null || attrs.Count == 0) 
       continue; 
      // Little kludgy here: use MemberwiseClone to clone the JsonProperty. 
      var clone = property.GetType().GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 
      foreach (var name in attrs.Cast<LegacyDataMemberNamesAttribute>().SelectMany(a => a.Names)) 
      { 
       if (properties.Any(p => p.PropertyName == name)) 
       { 
        Debug.WriteLine("Duplicate LegacyDataMemberNamesAttribute: " + name); 
        continue; 
       } 
       var newProperty = (JsonProperty)clone.Invoke(property, new object[0]); 
       newProperty.Readable = false; 
       newProperty.PropertyName = name; 
       properties.Add(newProperty); 
      } 
     } 

     return properties; 
    } 
} 

Beachten Sie, dass diese Implementierung erfordert nicht, dass die Klasse explizite Daten Vertragsattribut Annotation . Sie können diese Einschränkung hinzufügen, wenn Sie bevorzugen.

verwenden Sie es dann mit der folgenden JsonSerializerSettings:

var settings = new JsonSerializerSettings { ContractResolver = LegacyPropertyResolver.Instance }; 

Zum Beispiel:

[DataContract] 
class TestObject 
{ 
    [LegacyDataMemberNames("alpha", "omega")] 
    [DataMember(Name = "a")] 
    public int A { get; set; } 
} 

public static class JsonExtensions 
{ 
    public static void RenameProperty(this JObject obj, string oldName, string newName) 
    { 
     if (obj == null) 
      throw new NullReferenceException(); 
     var property = obj.Property(oldName); 
     if (property != null) 
     { 
      property.Replace(new JProperty(newName, property.Value)); 
     } 
    } 
} 

public class TestClass 
{ 

    public static void Test() 
    { 
     try 
     { 
      TestInner(); 
     } 
     catch (Exception ex) 
     { 
      Debug.Assert(false, ex.ToString()); // No assert 
      throw; 
     } 
    } 

    public static void TestInner() 
    { 
     var test = new TestObject { A = 42 }; 

     var settings = new JsonSerializerSettings { ContractResolver = LegacyPropertyResolver.Instance }; 
     var json = JObject.FromObject(test, JsonSerializer.CreateDefault(settings)); 

     if (json.SelectToken("alpha") != null || json.SelectToken("omega") != null) 
      throw new InvalidOperationException("Failed serialization"); 

     Test(test, json); 

     json.RenameProperty("a", "alpha"); 

     Test(test, json); 

     json.RenameProperty("alpha", "omega"); 

     Test(test, json); 
    } 

    private static void Test(TestObject test, JObject json) 
    { 
     var test1 = json.ToObject<TestObject>(JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = LegacyPropertyResolver.Instance })); 
     if (test1.A != test.A) 
      throw new InvalidOperationException("Failed deserialization"); 
     Console.WriteLine("Successfully deserialized: " + json.ToString(Formatting.None)); 
     Debug.WriteLine("Successfully deserialized: " + json.ToString(Formatting.None)); 
    } 
} 
+0

Mein Gott, wow, das ist eine unglaublich gründliche Antwort. Vielen Dank. – bboyle1234

1

Ich habe Ihren Code und modifiziert es zu meinem eigenen Stil, wie folgt aus:

[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false, Inherited = true)] 
    public class LegacyDataMemberNamesAttribute : Attribute { 

     public readonly string[] LegacyNames; 

     public LegacyDataMemberNamesAttribute(params string[] legacyNames) { 
      LegacyNames = legacyNames; 
     } 
    } 

    public class LegacyPropertyResolver : DefaultContractResolver { 

     // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons. 
     // http://www.newtonsoft.com/json/help/html/ContractResolver.htm 
     // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm 
     // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance." 

     public static readonly LegacyPropertyResolver Instance = new LegacyPropertyResolver(); 

     protected LegacyPropertyResolver() : base() { } 

     protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization) { 
      var properties = base.CreateProperties(type, memberSerialization); 
      foreach (var property in properties.ToArray()) { 
       if (!property.Writable) continue; 
       foreach (var legacyName in GetLegacyNames(property)) { 
        properties.Add(CloneWithLegacyName(property, legacyName)); 
       } 
      } 
      return properties; 
     } 

     static IEnumerable<string> GetLegacyNames(JsonProperty property) { 
      return property.AttributeProvider.GetAttributes(typeof(LegacyDataMemberNamesAttribute), true) 
        .Cast<LegacyDataMemberNamesAttribute>() 
        .SelectMany(a => a.LegacyNames) 
        .Distinct(); 
     } 

     static readonly object[] _emptyObjectArray = new object[0]; 
     static readonly MethodInfo _propertyClone = typeof(JsonProperty).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 
     static JsonProperty CloneWithLegacyName(JsonProperty property, string legacyName) { 
      var legacyProperty = (JsonProperty)_propertyClone.Invoke(property, _emptyObjectArray); 
      legacyProperty.Readable = false; 
      legacyProperty.PropertyName = legacyName; 
      return legacyProperty; 
     } 
    }