当具体类包含其他接口时,如何反序列化接口集合 [英] How to deserialize collection of interfaces when concrete classes contains other interfaces

查看:61
本文介绍了当具体类包含其他接口时,如何反序列化接口集合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正面临一个无法修改的json文件的情况,并且我希望生成的反序列化类对于设计目的是通用的.

I am currently facing the situation where I get a json file that I can't modify and I want the resulting deserialized class to be generic for design purpose.

首先这是我的界面:

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

然后这是我设计用来解决问题的代码:

Then here is the code I design to tackle my problem :

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

如您所见,在IItemModel界面中有一个ICamSettings列表.

As you can see there is a list of ICamSettings in the IItemModel interface.

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

I try to deserialize this json into my ThumbnailJobModel class :

{
 "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:

I deserialize my json like that :

//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

老实说,我不明白自己在做什么,我希望有人能够对此有所启发.

I honestly don't understand what I am doing wrong, I hope someone will be able to throw some light on it.

推荐答案

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

Your basic problem is that your ConcreteConverter<I, T> is designed to deserialize something declared as an interface as a concrete type -- e.g. IItemModel as Item -- but you are not using it in that way. You are using it to deserialize a concrete list of interfaces as a concrete list of concrete types, e.g.:

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

相反,您应该使用

Instead, you should apply the converter to the items of the Items and CamSettings collections using JsonPropertyAttribute.ItemConverterType like so:

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

这应该解决异常.但是,还有其他建议:

This should fix the exception. However, there are additional suggestions to be made:

  • In several converters you have no implementation for WriteJson(). If you want to use default serialization, you can override CanWrite and return false.

请将TypeConverter重命名为ExportTypeConverter. TypeConverter已用于其他.

Please rename TypeConverter to ExportTypeConverter. TypeConverter is already used for something else.

OrientationConverterClothShapeConverter,内置的

OrientationConverter and ClothShapeConverter are unnecessary, the built-in StringEnumConverter will serialize and deserialize any enum as a string.

如果希望为数字枚举值引发异常,则可以将其子类化为StrictStringEnumConverter并设置

If you want an exception to be thrown for numeric enum values, you could subclass it as StrictStringEnumConverter and set AllowIntegerValues = false:

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

您还可以使ExportTypeConverterStringEnumConverter继承,以获得所需的序列化行为.

You could also make ExportTypeConverter inherit from StringEnumConverter so as to get the desired serialization behavior.

ConcreteConverter中,由于应该将T作为I的具体实现,因此可以添加where约束以确保该类型的用户不会意外地反转通用参数:

In ConcreteConverter since T is supposed to be a concrete implementation of I, you can add a where constraint to make sure users of the type don't accidentally invert the generic arguments:

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

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

I also renamed the generic arguments to something more meaningful.

在多个转换器中,您覆盖CanConvert(Type)并测试传入类型为string,其中string是序列化到文件的类型:

In several converters you override CanConvert(Type) and test for the incoming type being a string, where string was the type serialized to the file:

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

当直接由属性应用时,永远不会调用CanConvert().通过设置应用时,在序列化期间objectType是将要序列化的对象的实际类型.当通过设置应用时,反序列化期间objectType是其值将要反序列化的成员的声明类型.它永远不是文件中的类型.因此,在ExportTypeConverter中,其内容应如下所示:

When applied directly by attributes, CanConvert() is never called. When applied by settings, during serialization objectType is the actual type of the object that is about to get serialized. And when applied by settings, during deserialization objectType is the declared type of the member whose value is about to get deserialized. It's never the type in the file. Thus in ExportTypeConverter it should be written as follows:

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

或者,由于转换器仅通过属性应用,因此您可以抛出NotImplementedException.

Or, since the converter is only ever applied by attributes, you could just throw a NotImplementedException.

我看不到将Item之类的模型嵌套在ThumbnailJobModel中的任何理由.对我来说,这只会导致额外的复杂性.您可以改为将它们设为非公开.但这只是见仁见智.

I don't see any reason to nest models like Item inside ThumbnailJobModel. To me it simply causes additional complexity. You could just make them non-public instead. But this is just a matter of opinion.

将所有代码放在一起应该看起来像:

Putting all that together you code should look something like:

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

示例工作 .Net小提琴.

这篇关于当具体类包含其他接口时,如何反序列化接口集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆