【问题标题】:How to deserialize collection of interfaces when concrete classes contains other interfaces当具体类包含其他接口时如何反序列化接口集合
【发布时间】:2017-12-22 10:24:57
【问题描述】:

我目前面临的情况是,我得到一个无法修改的 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;
    }
}

如你所见,IItemModel 接口中有一个ICamSettings 的列表。

我尝试将此 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

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

【问题讨论】:

    标签: c# json.net deserialization


    【解决方案1】:

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

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

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

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

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

    • 在几个转换器中,您覆盖 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;
        }
    }
    

    样本工作.Net fiddle

    【讨论】:

    • 你好 dbc,真的很抱歉耽搁了:/ 超级容易理解和绝对出色的解决方案,它就像一个魅力!非常感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-08
    • 1970-01-01
    • 1970-01-01
    • 2014-07-01
    • 1970-01-01
    相关资源
    最近更新 更多