当具体类包含其他接口时,如何反序列化接口集合

时间:2017-12-22 10:24:58

标签: c# json.net deserialization

我目前面临的情况是,我得到一个我无法修改的json文件,并且我希望生成的反序列化类在设计目的中是通用的。

首先是我的界面:

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

如您所见,ICamSettings界面中有IItemModel列表。

我尝试将此json反序列化为ThumbnailJobModel类:

{
 "clientBaseURL":"https://clientName.fr",
 "userEmail":"myName@gmail.com",
 "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"
         }
      ]
   }
 ]
}

我将我的json反序列化:

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

抛出以下异常:

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

老实说,我不明白我做错了什么,我希望有人能够对它有所了解。

1 个答案:

答案 0 :(得分:0)

您的基本问题是您的ConcreteConverter<I, T>旨在将声明为接口的内容反序列化为具体类型 - 例如IItemModelItem - 但您没有以这种方式使用它。您正在使用它将具体的接口列表反序列化为具体类型的具体列表,例如:

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

相反,您应该使用JsonPropertyAttribute.ItemConverterType将转换器应用于ItemsCamSettings集合的,如下所示:

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

并且

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

这应该可以修复异常。但是,还有其他建议:

  • 在多个转换器中,您没有WriteJson()的实现。如果要使用默认序列化,可以override CanWrite and return false

  • 请将TypeConverter重命名为ExportTypeConverterTypeConverter已用于something else

  • OrientationConverterClothShapeConverter是不必要的,内置的StringEnumConverter会将任何枚举序列化和反序列化为字符串。

    如果要为数字枚举值抛出异常,可以将其子类化为StrictStringEnumConverter并设置AllowIntegerValues = false

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

    您还可以ExportTypeConverter继承StringEnumConverter以获得所需的序列化行为。

  • ConcreteConverter T,因为I应该是where的具体实现,您可以添加public class ConcreteConverter<IInterface, TConcrete> : JsonConverter where TConcrete : IInterface { } 约束以确保类型为don的用户不小心颠倒了泛型论点:

    CanConvert(Type)

    我还将通用参数重命名为更有意义的东西。

  • 在多个转换器中,您覆盖string并测试传入类型是string,其中public override bool CanConvert( Type objectType ) { return objectType == typeof( string ); } 是序列化到文件的类型:

    CanConvert()

    直接按属性应用时,永远不会调用objectType。通过设置应用时,在序列化期间objectType是即将序列化的对象的实际类型。当通过设置应用时,在反序列化期间ExportTypeConverter是其值将要反序列化的成员的声明类型。它永远不是文件中的类型。因此在public override bool CanConvert(Type objectType) { return objectType == typeof(ExportType); } 中应该写成如下:

    NotImplementedException

    或者,由于转换器仅由属性应用,因此您只需抛出Item

  • 我认为没有理由在ThumbnailJobModel内嵌套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; } } 等模型。对我而言,这只会带来额外的复杂性你可以把它们变成非公开的。但这只是一个意见问题。

将所有这些组合在一起代码应该类似于:

firstname.lastname@john-doe-university.example

示例工作.Net fiddle