2016-06-18 5 views
3

Ich muss meine Klasse zu JSON konvertieren und ich verwende Json.NET. Aber ich kann verschiedene JSON Strukturen haben, wie:Json.NET andere JSON-Struktur, basierend auf Enum-Wert

{ 
    name: "Name", 
    type: "simple1", 
    value: 100 
}; 

oder

{ 
    name: "Name", 
    type: { 
     optional1: { 
      setting1: "s1", 
      setting2: "s2", 
      ///etc. 
    }, 
    value: 100 
}; 

Mein C# -Code ist:

public class Configuration 
{ 
    [JsonProperty(PropertyName = "name")] 
    public string Name{ get; set; } 

    [JsonProperty(PropertyName = "type")] 
    public MyEnumTypes Type { get; set; } 

    public OptionalType TypeAdditionalData { get; set; } 

    [JsonProperty(PropertyName = "value")] 
    public int Value { get; set; } 
    public bool ShouldSerializeType() 
    { 
     OptionalSettingsAttribute optionalSettingsAttr = this.Type.GetAttributeOfType<OptionalSettingsAttribute>(); 
     return optionalSettingsAttr == null; 
    } 

    public bool ShouldSerializeTypeAdditionalData() 
    { 
     OptionalSettingsAttribute optionalSettingsAttr = this.Type.GetAttributeOfType<OptionalSettingsAttribute>(); 
     return optionalSettingsAttr != null; 
    } 
} 

public enum MyEnumTypes 
{ 
    [EnumMember(Value = "simple1")] 
    Simple1, 

    [EnumMember(Value = "simple2")] 
    Simple2, 

    [OptionalSettingsAttribute] 
    [EnumMember(Value = "optional1")] 
    Optional1, 

    [EnumMember(Value = "optional2")] 
    [OptionalSettingsAttribute] 
    Optional2 
} 

Meine Idee war es, wenn Configuration.Type - Wert nicht OptionalSettingsAttribute Attribut - um es als type: "simple1" zu serialisieren. Ansonsten - um Configuration.Type zu verwenden - Wert als Wertschlüssel des Typs (type: { optional1: {} }) und Wert in Configuration.TypeAdditionalData als optional1 - Wert (wie 2 einfache JSON oben).

Ich habe versucht, einen benutzerdefinierten Konverter zu erstellen, wie:

public class ConfigurationCustomConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return typeof(Configuration).IsAssignableFrom(objectType); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     return serializer.Deserialize<Configuration>(reader); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     //my changes here 

     serializer.Serialize(writer, value); 
    } 

Aber wenn ich hinzufügen [JsonConverter(typeof(ConfigurationCustomConverter))] Attribut Configuration Klasse:

[JsonConverter(typeof(ConfigurationCustomConverter))] 
public class Configuration 

und rief JsonConvert.SerializeObject(configurationObj); ich nächste Fehler empfangen:

Self referencing loop detected with type 'Configuration'. Path ''.

Haben Sie irgendwelche Ideen, wie Sie sich ändern können? mein Code, um meine Klasse in 2 verschiedene JSON-Strukturen zu serialisieren? Hinweis: Ich werde nicht die gleiche Klasse zum Deserialisieren des JSON verwenden.

Vielen Dank!

Antwort

2

Der Grund, warum Sie die Self referencing loop detected Ausnahme erhalten, dass die WriteJson Methode des Wandlers selbst rekursiv aufruft. Wenn Sie einen Konverter auf einen Typ anwenden, der [JsonConverter(typeof(ConfigurationCustomConverter))] verwendet, wird die WriteJson() Methode bedingungslos ersetzen Json.NET die Standardimplementierung. Also Ihr innerer Anruf:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
{ 
    //my changes here 
    serializer.Serialize(writer, value); 
} 

würde einen Stapelüberlauf verursachen. Json.NET bemerkt dies und wirft stattdessen die Ausnahme, die Sie sehen. Weitere Informationen finden Sie unter JSON.Net throws StackOverflowException when using [JsonConvert()]. Die Einstellung ReferenceLoopHandling.Ignore bewirkt einfach, dass die unendliche Rekursion übersprungen wird und Ihr Objekt leer bleibt.

Sie haben ein paar Optionen, um dieses Problem zu lösen:

  1. Sie manuell alle Eigenschaftsnamen und andere Werte als Type schreiben können und TypeAdditionalData dann zuletzt die benutzerdefinierten "type" Eigenschaft schreiben. Zum Beispiel:

    [JsonConverter(typeof(ConfigurationConverter))] 
    public class Configuration 
    { 
        [JsonProperty(PropertyName = "name")] 
        public string Name { get; set; } 
    
        public MyEnumTypes Type { get; set; } 
    
        public OptionalType TypeAdditionalData { get; set; } 
    
        [JsonProperty(PropertyName = "value")] 
        public int Value { get; set; } 
    } 
    
    class ConfigurationConverter : JsonConverter 
    { 
        const string typeName = "type"; 
    
        public override bool CanConvert(Type objectType) 
        { 
         return typeof(Configuration).IsAssignableFrom(objectType); 
        } 
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
        { 
         if (reader.TokenType == JsonToken.Null) 
          return null; 
         var config = (existingValue as Configuration ?? (Configuration)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator()); 
    
         // Populate the regular property values. 
         var obj = JObject.Load(reader); 
         var type = obj.RemoveProperty(typeName); 
         using (var subReader = obj.CreateReader()) 
          serializer.Populate(subReader, config); 
    
         // Populate Type and OptionalType 
         if (type is JValue) // Primitive value 
         { 
          config.Type = type.ToObject<MyEnumTypes>(serializer); 
         } 
         else 
         { 
          var dictionary = type.ToObject<Dictionary<MyEnumTypes, OptionalType>>(serializer); 
          if (dictionary.Count > 0) 
          { 
           config.Type = dictionary.Keys.First(); 
           config.TypeAdditionalData = dictionary.Values.First(); 
          } 
         } 
    
         return config; 
        } 
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
        { 
         var config = (Configuration)value; 
         var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(config.GetType()); 
         writer.WriteStartObject(); 
         foreach (var property in contract.Properties 
          .Where(p => p.Writable && (p.ShouldSerialize == null || p.ShouldSerialize(config)) && !p.Ignored)) 
         { 
          if (property.UnderlyingName == "Type" || property.UnderlyingName == "TypeAdditionalData") 
           continue; 
          var propertyValue = property.ValueProvider.GetValue(config); 
          if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore) 
           continue; 
          writer.WritePropertyName(property.PropertyName); 
          serializer.Serialize(writer, propertyValue); 
         } 
         writer.WritePropertyName(typeName); 
         if (config.Type.GetCustomAttributeOfEnum<OptionalSettingsAttribute>() == null) 
         { 
          serializer.Serialize(writer, config.Type); 
         } 
         else 
         { 
          var dictionary = new Dictionary<MyEnumTypes, OptionalType> 
          { 
           { config.Type, config.TypeAdditionalData }, 
          }; 
          serializer.Serialize(writer, dictionary); 
         } 
         writer.WriteEndObject(); 
        } 
    } 
    
    public class OptionalType 
    { 
        public string setting1 { get; set; } 
    } 
    
    public class OptionalSettingsAttribute : System.Attribute 
    { 
        public OptionalSettingsAttribute() 
        { 
        } 
    } 
    
    [JsonConverter(typeof(StringEnumConverter))] 
    public enum MyEnumTypes 
    { 
        [EnumMember(Value = "simple1")] 
        Simple1, 
    
        [EnumMember(Value = "simple2")] 
        Simple2, 
    
        [OptionalSettingsAttribute] 
        [EnumMember(Value = "optional1")] 
        Optional1, 
    
        [EnumMember(Value = "optional2")] 
        [OptionalSettingsAttribute] 
        Optional2 
    } 
    
    public static class EnumExtensions 
    { 
        public static TAttribute GetCustomAttributeOfEnum<TAttribute>(this Enum value) 
         where TAttribute : System.Attribute 
        { 
         var type = value.GetType(); 
         var memInfo = type.GetMember(value.ToString()); 
         return memInfo[0].GetCustomAttribute<TAttribute>(); 
        } 
    } 
    
    public static class JsonExtensions 
    { 
        public static JToken RemoveProperty(this JObject obj, string name) 
        { 
         if (obj == null) 
          return null; 
         var property = obj.Property(name); 
         if (property == null) 
          return null; 
         var value = property.Value; 
         property.Remove(); 
         property.Value = null; 
         return value; 
        } 
    } 
    

    Hinweis habe ich [JsonConverter(typeof(StringEnumConverter))] auf Ihre Enum. Dadurch wird sichergestellt, dass der Typ immer als Zeichenfolge geschrieben wird.

    Beispiel fiddle.

  2. Sie können rekursive Aufrufe an den Konverter über die in JSON.Net throws StackOverflowException when using [JsonConvert()] gezeigte Technik deaktivieren, eine Standardserialisierung generieren, sie nach Bedarf ändern und aufschreiben.

  3. Sie könnten die Verwendung eines Wandlers vollständig vermeiden, indem Type und TypeAdditionalData als [JsonIgnore] Markierung und eine zusätzliche Privateigentum Einführung "type" serialisiert und deserialisiert: Danke

    public class Configuration 
    { 
        [JsonProperty(PropertyName = "name")] 
        public string Name { get; set; } 
    
        [JsonIgnore] 
        public MyEnumTypes Type { get; set; } 
    
        [JsonIgnore] 
        public OptionalType TypeAdditionalData { get; set; } 
    
        [JsonProperty("type")] 
        JToken SerializedType 
        { 
         get 
         { 
          if (Type.GetCustomAttributeOfEnum<OptionalSettingsAttribute>() == null) 
          { 
           return JToken.FromObject(Type); 
          } 
          else 
          { 
           var dictionary = new Dictionary<MyEnumTypes, OptionalType> 
           { 
            { Type, TypeAdditionalData }, 
           }; 
           return JToken.FromObject(dictionary); 
          } 
         } 
         set 
         { 
          if (value == null || value.Type == JTokenType.Null) 
          { 
           TypeAdditionalData = null; 
           Type = default(MyEnumTypes); 
          } 
          else if (value is JValue) 
          { 
           Type = value.ToObject<MyEnumTypes>(); 
          } 
          else 
          { 
           var dictionary = value.ToObject<Dictionary<MyEnumTypes, OptionalType>>(); 
           if (dictionary.Count > 0) 
           { 
            Type = dictionary.Keys.First(); 
            TypeAdditionalData = dictionary.Values.First(); 
           } 
          } 
         } 
        } 
    
        [JsonProperty(PropertyName = "value")] 
        public int Value { get; set; } 
    } 
    
+0

Vielen Dank @dbc viel! Ich habe Lösung 3 versucht und es funktioniert für mich! – Pepo

1

Wenn Sie über diesen Fehler hinausgehen müssen, können Sie Ihre Serialisierung so konfigurieren, dass die Referenzschleife ignoriert wird. Dies geschieht mit einer der SerializaObject() Überladungen.

JsonConvert.SerializeObject(configurationObj, 
        new JsonSerializerSettings() 
        { 
         ReferenceLoopHandling = ReferenceLoopHandling.Ignore 
        }); 
+0

! Dies hat den Fehler "Reference loop" behoben, aber wenn 'WriteJson' Methode folgenden Code hat: ' public override void WriteJson (JsonWriter writer, Objektwert, JsonSerializer Serializer) { Serializer.Serialize (writer, value); } ' die serialisierte Zeichenfolge ist leer - nichts hat konvertiert. Ist es richtig? Wie kann ich den Standardkonverter in meinem benutzerdefinierten Konverter verwenden? Vielen Dank! – Pepo

+0

Ich bin nicht auf die Situation gestoßen, die Sie beschreiben, und ich bin nicht neben meinem Computer, um mehr zu untersuchen. Ich werde am Montag sein. Hoffentlich kann vorher jemand anderes helfen. Bitte aktualisieren Sie Ihre Frage mit dem Fortschritt, den Sie mit den von mir bereitgestellten Informationen gemacht haben, damit die nächste Person die vollständige Frage beantworten kann. –

+1

Danke! Ich habe die Lösung # 3 in @dbc verwendet - Antwort und es funktioniert für mich! – Pepo