2017-12-22 2 views
1

Ich bin derzeit mit der Situation konfrontiert, wo ich eine JSON-Datei, die ich nicht ändern kann, und ich möchte die resultierende deserialized-Klasse für Designzwecke generisch sein.Wie Deserialisierung der Auflistung von Schnittstellen, wenn konkrete Klassen andere Schnittstellen enthält

Zuerst sind hier meine Schnittstellen:

public interface IJobModel 
{ 
    string ClientBaseURL { get; set; } 
    string UserEmail { get; set; } 
    ExportType Type { get; set; } 
    List<IItemModel> Items { get; set; } 
} 

public interface IItemModel 
{ 
    string Id { get; set; } 
    string ImageSize { get; set; } 
    string ImagePpi { get; set; } 
    List<ICamSettings> CamSettings { get; set; } 
} 

public interface ICamSettings 
{ 
    string FileName { get; set; } 
} 

hier Dann ist der Code Ich entwerfe mein Problem zu lösen:

public class ThumbnailJobModel : IJobModel 
{ 
    [JsonProperty("clientBaseURL")] 
    public string ClientBaseURL { get; set; } 

    [JsonProperty("userEmail")] 
    public string UserEmail { get; set; } 

    [JsonProperty("type")] 
    [JsonConverter(typeof(TypeConverter))] 
    public ExportType Type { get; set; } 

    [JsonProperty("items")] 
    [JsonConverter(typeof(ConcreteConverter<List<IItemModel>, List<Item>> 
))] 
    public List<IItemModel> Items { get; set; } 

    public ThumbnailJobModel() 
    { 
     Type = ExportType.Thumbnails; 
     Items = new List<IItemModel>(); 
    } 

    public class Item : IItemModel 
    { 
     [JsonProperty("id")] 
     public string Id { get; set; } 

     [JsonProperty("imageSize")] 
     public string ImageSize { get; set; } 

     [JsonProperty("imagePpi")] 
     public string ImagePpi { get; set; } 

     [JsonProperty("shoots")] 
     //[JsonConverter(typeof(CamSettingsConverter))] 
     [JsonConverter(typeof(ConcreteConverter<List<ICamSettings>, 
List<ShootSettings>>))] 
     public List<ICamSettings> CamSettings { get; set; } 

     public Item() 
     { 
      CamSettings = new List<ICamSettings>(); 
     } 
    } 

    public class ShootSettings : ICamSettings 
    { 
     [JsonProperty("orientation")] 
     [JsonConverter(typeof(OrientationConverter))] 
     public Orientation Orientation { get; set; } 

     [JsonProperty("clothShape")] 
     [JsonConverter(typeof(ClothShapeConverter))] 
     public Shape Shape { get; set; } 

     [JsonProperty("fileName")] 
     public string FileName { get; set; } 

     public ShootSettings() 
     { 
      Orientation = Orientation.Perspective; 
      Shape = Shape.Folded; 
      FileName = null; 
     } 
    } 

    public enum Orientation 
    { 
     Perspective = 0, 
     Oblique = 1, 
     Front = 2, 
     Back = 3, 
     Left = 4, 
     Right = 5, 
     Up = 6, 
     Down = 7 
    } 

    public enum Shape 
    { 
     Folded = 0, 
     Hanger = 1, 
     Mannequin = 2 
    } 

    public class ConcreteConverter<I, T> : JsonConverter 
    { 
     public override bool CanConvert(Type objectType) 
     { 
      return typeof(I) == objectType; 
     } 

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

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

    public class OrientationConverter : JsonConverter 
    { 
     public override object ReadJson(JsonReader reader, Type objectType, 
object existingValue, JsonSerializer serializer) 
     { 
      string enumString = (string)reader.Value; 

      return Enum.Parse(typeof(Orientation), enumString, true); 
     } 

     public override bool CanConvert(Type objectType) 
     { 
      return objectType == typeof(string); 
     } 

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

    public class ClothShapeConverter : JsonConverter 
    { 
     public override object ReadJson(JsonReader reader, Type objectType, 
object existingValue, JsonSerializer serializer) 
     { 
      var enumString = (string)reader.Value; 

      return Enum.Parse(typeof(Shape), enumString, true); 
     } 

     public override bool CanConvert(Type objectType) 
     { 
      return objectType == typeof(string); 
     } 

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

    public class TypeConverter : JsonConverter 
    { 
     public override object ReadJson(JsonReader reader, Type objectType, 
object existingValue, JsonSerializer serializer) 
     { 
      return ExportType.Thumbnails; 
     } 

     public override bool CanConvert(Type objectType) 
     { 
      return objectType == typeof(string); 
     } 

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

    public static void HandleDeserializationError(object sender, 
ErrorEventArgs errorArgs) 
    { 
     errorArgs.ErrorContext.Handled = true; 
     var currentObj = errorArgs.CurrentObject as ShootSettings; 

     if (currentObj == null) return; 

     currentObj.Orientation = Orientation.Perspective; 
     currentObj.Shape = Shape.Folded; 
    } 
} 

Wie man dort sehen kann eine Liste von ICamSettings in der IItemModel Schnittstelle .

ich versuchen, diese json in meine ThumbnailJobModel Klasse deserialisieren:

{ 
"clientBaseURL":"https://clientName.fr", 
"userEmail":"[email protected]", 
"items":[ 
    { 
     "id":"11913", 
     "imageSize":"1280,720", 
     "imagePpi":"72", 
     "shoots":[ 
     { 
      "fileName":"front1.png", 
      "orientation":"front", 
      "clothShape":"hanger" 
     }, 
     { 
      "fileName":"folded1.png", 
      "orientation":"front", 
      "clothShape":"folded" 
     }, 
     { 
      "fileName":"right1.png", 
      "orientation":"right", 
      "clothShape":"hanger" 
     } 
     ] 
    }, 
    { 
     "id":"2988", 
     "imageSize":"1280,720", 
     "imagePpi":"", 
     "shoots":[ 
     { 
      "fileName":"perspective1.png", 
      "orientation":"perspective" 
     } 
     ] 
    } 
] 
} 

ich deserialisieren meine Json wie folgt aus:

//Read the job config 
string jobConfig = File.ReadAllText(jsonConfigPath); 
IJobModel m_jobModel = JsonConvert.DeserializeObject<ThumbnailJobModel>( 
jobConfig); 

Und die folgende Ausnahme ausgelöst:

Exception : Error setting value to 'CamSettings' on 
'IWD.Screenshoter.Job.ThumbnailJobModel+Item'. 
Stack : 
    at Newtonsoft.Json.Serialization.DynamicValueProvider.SetValue 
(System.Object target, System.Object value) [0x00000] in <filename 
unknown>:0 
    at 
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue 
(Newtonsoft.Json.Serialization.JsonProperty property, 
Newtonsoft.Json.JsonConverter propertyConverter, 
Newtonsoft.Json.Serialization.JsonContainerContract containerContract, 
Newtonsoft.Json.Serialization.JsonProperty containerProperty, 
Newtonsoft.Json.JsonReader reader, System.Object target) [0x00000] in 
<filename unknown>:0 
    at 
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject 
(System.Object newObject, Newtonsoft.Json.JsonReader reader, 
Newtonsoft.Json.Serialization.JsonObjectContract contract, 
Newtonsoft.Json.Serialization.JsonProperty member, System.String id) 
[0x00000] in <filename unknown>:0 

Ich verstehe ehrlich gesagt nicht, was ich falsch mache, ich hoffe, dass jemand in der Lage sein wird, durchzudrehen w ein Licht darauf.

Antwort

0

Ihr grundlegendes Problem besteht darin, dass Ihr ConcreteConverter<I, T> entworfen ist, um etwas zu demerialisieren, das als eine Schnittstelle als konkreter Typ deklariert wird - z. IItemModel als Item - aber Sie verwenden es nicht auf diese Weise. Sie verwenden es eine konkrete Liste von Schnittstellen als eine konkrete Liste von konkreten Typen deserialisieren, zB:

[JsonProperty("items")] 
[JsonConverter(typeof(ConcreteConverter<List<IItemModel>, List<Item>>))] 
public List<IItemModel> Items { get; set; } 

Stattdessen sollten Sie den Konverter auf die Elemente gelten der Items und CamSettings Sammlungen JsonPropertyAttribute.ItemConverterType mit wie so :

public class ThumbnailJobModel : IJobModel 
{ 
    [JsonProperty("items", ItemConverterType = typeof(ConcreteConverter<IItemModel, Item>))] 
    public List<IItemModel> Items { get; set; } 

Und

public class Item : IItemModel 
{ 
    [JsonProperty("shoots", ItemConverterType = typeof(ConcreteConverter<ICamSettings, ShootSettings>))] 
    public List<ICamSettings> CamSettings { get; set; } 

Dies sollte das beheben Ausnahme. Allerdings gibt es weitere Vorschläge gemacht werden:

  • In mehreren Wandlern Sie keine Implementierung für WriteJson() haben. Wenn Sie die Standardserialisierung verwenden möchten, können Sie override CanWrite and return false.

  • Bitte benennen Sie TypeConverter in ExportTypeConverter um. TypeConverter wird bereits für something else verwendet.

  • OrientationConverter und ClothShapeConverter sind nicht notwendig, die eingebaute StringEnumConverter serialisiert und deserialisiert jede enum als String.

    Wenn Sie eine Ausnahme wollen für numerische ENUM-Werte geworfen werden, können Sie es als StrictStringEnumConverter Unterklasse und setzen AllowIntegerValues = false:

    public class StrictStringEnumConverter : StringEnumConverter 
    { 
        public StrictStringEnumConverter() { this.AllowIntegerValues = false; } 
    } 
    

    Sie auch ExportTypeConverter vererben StringEnumConverter, um zu bekommen das gewünschte Serialisierungsverhalten machen könnte .

  • In ConcreteConverter seit T soll eine konkrete Umsetzung von I sein, können Sie eine where Einschränkung hinzufügen sicher, dass Benutzer von der Art der generischen Argumente invertieren nicht versehentlich zu machen:

    public class ConcreteConverter<IInterface, TConcrete> : JsonConverter where TConcrete : IInterface 
    { 
    } 
    

    ich auch Die generischen Argumente wurden in etwas Bedeutsameres umbenannt.

  • In mehreren Wandlern Sie CanConvert(Type) und Test für den eingehenden Typ außer Kraft setzen eine string sein, wo string der Typ serialisiert in die Datei war:

    public override bool CanConvert(Type objectType) 
    { 
        return objectType == typeof(string); 
    } 
    

    Wenn direkt von Attributen angewendet wird CanConvert() nie genannt. Wenn durch Einstellungen angewendet, ist objectType während der Serialisierung der tatsächliche Typ des Objekts, das gerade serialisiert werden soll. Und wenn es durch Einstellungen angewendet wird, ist während der Deserialisierung objectType der deklarierte Typ des Elements, dessen Wert demerialisiert werden soll. Es ist nie der Typ in der Datei. So in ExportTypeConverter geschrieben werden soll, wie folgt:

    public override bool CanConvert(Type objectType) 
    { 
        return objectType == typeof(ExportType); 
    } 
    

    Oder, da der Konverter immer nur durch Attribute angewandt wird, könnte man einfach ein NotImplementedException werfen.

  • Ich sehe keinen Grund, Modelle wie Item innerhalb ThumbnailJobModel zu verschachteln. Für mich führt das einfach zu zusätzlicher Komplexität. Du könntest sie einfach nicht öffentlich machen. Aber das ist nur eine Frage der Meinung.

alle Putting, dass Sie Code zusammen sollten wie etwas aussehen:

public interface IJobModel 
{ 
    string ClientBaseURL { get; set; } 
    string UserEmail { get; set; } 
    ExportType Type { get; set; } 
    List<IItemModel> Items { get; set; } 
} 

public interface IItemModel 
{ 
    string Id { get; set; } 
    string ImageSize { get; set; } 
    string ImagePpi { get; set; } 
    List<ICamSettings> CamSettings { get; set; } 
} 

public interface ICamSettings 
{ 
    string FileName { get; set; } 
} 

public enum ExportType 
{ 
    Thumbnails, 
} 

public class ThumbnailJobModel : IJobModel 
{ 
    [JsonProperty("clientBaseURL")] 
    public string ClientBaseURL { get; set; } 

    [JsonProperty("userEmail")] 
    public string UserEmail { get; set; } 

    [JsonProperty("type")] 
    [JsonConverter(typeof(ExportTypeConverter))] 
    public ExportType Type { get; set; } 

    [JsonProperty("items", ItemConverterType = typeof(ConcreteConverter<IItemModel, Item>))] 
    public List<IItemModel> Items { get; set; } 

    public ThumbnailJobModel() 
    { 
     Type = ExportType.Thumbnails; 
     Items = new List<IItemModel>(); 
    } 

    public class Item : IItemModel 
    { 
     [JsonProperty("id")] 
     public string Id { get; set; } 

     [JsonProperty("imageSize")] 
     public string ImageSize { get; set; } 

     [JsonProperty("imagePpi")] 
     public string ImagePpi { get; set; } 

     [JsonProperty("shoots", ItemConverterType = typeof(ConcreteConverter<ICamSettings, ShootSettings>))] 
     public List<ICamSettings> CamSettings { get; set; } 

     public Item() 
     { 
      CamSettings = new List<ICamSettings>(); 
     } 
    } 

    public class ShootSettings : ICamSettings 
    { 
     [JsonProperty("orientation")] 
     [JsonConverter(typeof(StrictStringEnumConverter))] 
     public Orientation Orientation { get; set; } 

     [JsonProperty("clothShape")] 
     [JsonConverter(typeof(StrictStringEnumConverter))] 
     public Shape Shape { get; set; } 

     [JsonProperty("fileName")] 
     public string FileName { get; set; } 

     public ShootSettings() 
     { 
      Orientation = Orientation.Perspective; 
      Shape = Shape.Folded; 
      FileName = null; 
     } 
    } 

    public enum Orientation 
    { 
     Perspective = 0, 
     Oblique = 1, 
     Front = 2, 
     Back = 3, 
     Left = 4, 
     Right = 5, 
     Up = 6, 
     Down = 7 
    } 

    public enum Shape 
    { 
     Folded = 0, 
     Hanger = 1, 
     Mannequin = 2 
    } 

    public class ConcreteConverter<IInterface, TConcrete> : JsonConverter where TConcrete : IInterface 
    { 
     public override bool CanConvert(Type objectType) 
     { 
      return typeof(IInterface) == objectType; 
     } 

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

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

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

    public class ExportTypeConverter : StringEnumConverter 
    { 
     public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
     { 
      reader.Skip(); // Skip anything at the current reader's position. 
      return ExportType.Thumbnails; 
     } 

     public override bool CanConvert(Type objectType) 
     { 
      return objectType == typeof(ExportType); 
     } 
    } 

    public class StrictStringEnumConverter : StringEnumConverter 
    { 
     public StrictStringEnumConverter() { this.AllowIntegerValues = false; } 
    } 

    public static void HandleDeserializationError(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs errorArgs) 
    { 
     errorArgs.ErrorContext.Handled = true; 
     var currentObj = errorArgs.CurrentObject as ShootSettings; 

     if (currentObj == null) return; 

     currentObj.Orientation = Orientation.Perspective; 
     currentObj.Shape = Shape.Folded; 
    } 
} 

Probe .Net fiddle arbeiten.

+0

Hallo dbc, tut mir wirklich leid für die Verzögerung:/ Super einfach zu verstehen und absolut brillante Lösung, es funktioniert wie ein Charme! Vielen Dank. –

Verwandte Themen