2017-03-01 3 views
4

Ich weiß, dass es viele Posts über diese Art von Situation gibt, aber alle Antworten, die ich nachgeschlagen habe, funktionieren nicht für mich, und ich denke, meine Situation ist etwas anders ..NET kann verschachteltes Objekt beim Deserialisieren von JSON nicht analysieren

Ich habe eine Klasse als Property bekannt, und es ist eine Meta-Daten-Klasse, die den Wert der Eigenschaft beschreibt, und dann hat auch ein Attribut, das den tatsächlichen Wert enthält:

public sealed class PropertyValue 
{ 
    public PropertyValue() 
    { 

    }   

    public string PropertyName { get; set; } 

    public string CategoryName { get; } 

    public string DisplayName { get; } 

    public int PropertyId { get; } 

    public string TypeName { get; set;}   

    public string ToolTip { get; set;} 

    public string Description { get; }   

    public object CurrentValue { get; set; } 

} 

Die Typename-Eigenschaft sagt eigentlich, welche Art des Objekts CurrentValue sollte sein, und die Werte reichen von System.Int32 bis Proprietäre Objekte, die unsere Firma aufgebaut hat. Das Problem ist, wenn ich versuche, JsonConvert.DeserializeObject (Eigenschaft) zu verwenden, es alles außer der CurrentValue-Eigenschaft deserialisiert. Ich habe versucht, eine switch-Anweisung im Konstruktor für alle von uns behandelten Typen zu verwenden und eine neue Instanz dieser Klasse zu erstellen, aber die geschachtelten Werte in JSON werden nicht aufgelöst.

Irgendwelche Ideen?

Edit: Ich schließe meine JSON, die eine unserer Zeitzone Klassen zeigt:

{ 
    "PropertyName":"TimeZone", 
    "CategoryName":"TBD", 
    "DisplayName":"TimeZone", 
    "PropertyId":15, 
    "TypeName":"Namespace.TimeZoneReference", 
    "ToolTip":"", 
    "Description":"", 
    "CurrentValue":{ 
     "timeZoneID":21, 
     "timeZoneName":"Eastern Standard Time" 
    } 
} 
+1

ist der CurrentValue Null im Debugger oder wirft es Sie einen Fehler? – Coder

+0

Es zeigt tatsächlich einen Wert in der JSON-Zeichenfolge, die gelesen wird, aber nach Deserialisierung, zeigt es nur als Zeichenfolge Wert mit geschweiften Klammern um die zwei oder drei Schlüssel/Wert-Paare gewickelt –

+0

können Sie Ihre JSON teilen? – Coder

Antwort

3

Es klingt wie Sie versuchen, Json.NET TypeNameHandling setting neu zu erfinden. Da Sie diese Einstellung nicht verwenden, sondern stattdessen den Typnamen für CurrentValue selbst serialisieren, müssen Sie custom JsonConverter erstellen, um Ihre PropertyValue und Deserialize CurrentValue mit dem gewünschten Typ zu füllen. Ohne diesen wird JSER.NET den aktuellen Wert entweder für ein primitives Objekt (wie long oder string) oder ein LINQ to JSON Objekt wie JObject für einen nicht primitiven JSON-Wert deserialisieren. (Letzteres ist der String-Wert mit geschweiften Klammern eingewickelt um die zwei oder drei Schlüssel/Wert-Paare, die Sie in den Kommentaren zu sehen erwähnen.)

ist hier ein möglicher Konverter auf Ihren Typ angewendet:

[JsonConverter(typeof(PropertyValueConverter))] 
public sealed class PropertyValue 
{ 
    public PropertyValue(object CurrentValue) 
    { 
     SetCurrentValue(CurrentValue); 
    } 

    public PropertyValue() 
    { 
    } 

    public string PropertyName { get; set; } 

    public string CategoryName { get; set; } 

    public string DisplayName { get; set; } 

    public int PropertyId { get; set; } 

    public string TypeName { get; set; } 

    public string ToolTip { get; set; } 

    public string Description { get; set; } 

    public object CurrentValue { get; set; } 

    public void SetCurrentValue(object value) 
    { 
     CurrentValue = value; 
     if (value == null) 
      TypeName = null; 
     else 
      TypeName = value.GetType().AssemblyQualifiedName; 
    } 
} 

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     if (reader.TokenType == JsonToken.Null) 
      return null; 
     var propertyValue = (existingValue as PropertyValue ?? new PropertyValue()); 

     var obj = JObject.Load(reader); 

     // Remove the CurrentValue property for manual deserialization, and deserialize 
     var jValue = obj.GetValue("CurrentValue", StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent(); 

     // Load the remainder of the properties 
     serializer.Populate(obj.CreateReader(), propertyValue); 

     // Convert the type name to a type. 
     // Use the serialization binder to sanitize the input type! See 
     // https://stackoverflow.com/questions/39565954/typenamehandling-caution-in-newtonsoft-json 

     if (!string.IsNullOrEmpty(propertyValue.TypeName) && jValue != null) 
     { 
      string typeName, assemblyName; 
      ReflectionUtils.SplitFullyQualifiedTypeName(propertyValue.TypeName, out typeName, out assemblyName); 

      var type = serializer.Binder.BindToType(assemblyName, typeName); 
      if (type != null) 
       propertyValue.SetCurrentValue(jValue.ToObject(type, serializer)); 
     } 

     return propertyValue; 
    } 

    public override bool CanWrite { get { return false; } } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 
} 

public static class JsonExtensions 
{ 
    public static JToken RemoveFromLowestPossibleParent(this JToken node) 
    { 
     if (node == null) 
      return null; 
     var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault(); 
     if (contained != null) 
      contained.Remove(); 
     // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should 
     if (node.Parent is JProperty) 
      ((JProperty)node.Parent).Value = null; 
     return node; 
    } 
} 

public static class ReflectionUtils 
{ 
    // Utilities taken from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs 
    // I couldn't find a way to access these directly. 

    public static void SplitFullyQualifiedTypeName(string fullyQualifiedTypeName, out string typeName, out string assemblyName) 
    { 
     int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(fullyQualifiedTypeName); 

     if (assemblyDelimiterIndex != null) 
     { 
      typeName = fullyQualifiedTypeName.Substring(0, assemblyDelimiterIndex.GetValueOrDefault()).Trim(); 
      assemblyName = fullyQualifiedTypeName.Substring(assemblyDelimiterIndex.GetValueOrDefault() + 1, fullyQualifiedTypeName.Length - assemblyDelimiterIndex.GetValueOrDefault() - 1).Trim(); 
     } 
     else 
     { 
      typeName = fullyQualifiedTypeName; 
      assemblyName = null; 
     } 
    } 

    private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName) 
    { 
     int scope = 0; 
     for (int i = 0; i < fullyQualifiedTypeName.Length; i++) 
     { 
      char current = fullyQualifiedTypeName[i]; 
      switch (current) 
      { 
       case '[': 
        scope++; 
        break; 
       case ']': 
        scope--; 
        break; 
       case ',': 
        if (scope == 0) 
        { 
         return i; 
        } 
        break; 
      } 
     } 

     return null; 
    } 
} 

Probe fiddle. (Ich musste mehrere Ihrer Eigenschaften machen lesen/schreiben, da sie schreibgeschützt wurden, aber nicht im Konstruktor festgelegt.)

Alternativ können Sie markieren Sie Ihre CurrentValue mit [JsonProperty(TypeNameHandling = TypeNameHandling.All)]:

public sealed class PropertyValue 
{ 
    [JsonProperty(TypeNameHandling = TypeNameHandling.All)] 
    public object CurrentValue { get; set; } 

    // Remainder as before 

Nachdem dies getan eine "$type" Eigenschaft Ausgabe die tatsächliche Art der CurrentObject angibt, und automatisch diese Art bei der Deserialisierung verwenden, zB Json.NET wird:

{ 
    "CurrentValue": { 
     "$type": "Question42537050.ExampleClass1, Tile", 
     "Foo": "hello" 
    }, 
    "PropertyName": "name1", 
    "CategoryName": null, 
    "DisplayName": null, 
    "PropertyId": 0, 
    "TypeName": "Question42537050.ExampleClass1, Tile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", 
    "ToolTip": "tip1", 
    "Description": null 
    } 

natürlich, wenn Sie dies tun, die t Ihr Name erscheint zweimal im JSON - einmal für Ihre TypeName Eigenschaft und einmal für Json.NETs $type Eigenschaft. Und diese Einstellung funktioniert nur für komplexe Objekte und Arrays, nicht für primitive Typen.

In beiden Fällen sollten Sie aus Sicherheitsgründen Ihre vor dem Erstellen einer Instanz des Typs aus Gründen, die here erläutert werden, bereinigen. Mein Code geht davon aus, dass Sie JsonSerializer.Binder eingerichtet haben, dies mit einem custom SerializationBinder zu tun, aber Sie können stattdessen einige Validierungslogik in PropertyValueConverter.ReadJson() selbst implementieren.

+0

Danke! Ich musste einige Änderungen wegen proprietärer Dinge vornehmen, aber das hat perfekt funktioniert! –

0

Wenn Sie null Current dann würde ich prüfen, bekommen, um zu sehen, ob Fehler bei der Deserialisierung geworfen. Wenn CurrentValue tatsächlich einen Wert hat (ich vermute, dass ein stringed Objekt vielleicht?), Dann müssen Sie eine benutzerdefinierte JsonConverter schreiben, um es das gewünschte Objekt zu erhalten.

Verwandte Themen