当混凝土类包含其他界面时,如何进行接口的收集



我当前正在面对我无法修改的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

老实说,我不明白我在做什么错,希望有人能够阐明它。

您的基本问题是,您的ConcreteConverter<I, T>旨在将被声明为接口称为混凝土类型的事物进行挑选,例如IItemModelItem-但您并非以这种方式使用它。您正在使用它来将接口的具体列表作为具体类型的具体列表,例如:

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

相反,您应该使用 JsonPropertyAttribute.ItemConverterTypeItemsCamSettings收集的项目将转换器应用于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; }

这应该解决例外。但是,还有其他建议:

  • 在几个转换器中,您没有针对WriteJson()的实现。如果要使用默认序列化,则可以覆盖CanWrite并返回false

  • 请将TypeConverter重命名为ExportTypeConverterTypeConverter已经用于其他东西。

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

    如果要为数字枚举值抛出一个异常,则可以将其作为StrictStringEnumConverter子群,并设置AllowIntegerValues = false

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

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

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

    or,由于转换器仅通过属性应用,因此您只能扔一个NotImplementedException

  • 我看不到ThumbnailJobModelItem之类的嵌套模型的理由。对我来说,这只是引起额外的复杂性。您可以将它们变成非公共。但这只是一个意见。

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

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小提琴。

最新更新