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

3

我目前面临的情况是,我得到了一个无法修改的 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: ``` 我这样反序列化我的 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个回答

2
你的基本问题在于,你的ConcreteConverter<I, T>旨在将声明为接口的内容反序列化为具体类型,例如将IItemModel反序列化为Item,但你并没有以这种方式使用它。你正在使用它将一个具体接口列表反序列化为一个具体类型列表,例如:
[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; }

And

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

这应该可以解决异常问题。然而,还有其他建议需要提出:
  • In several converters you have no implementation for WriteJson(). If you want to use default serialization, you can override CanWrite and return false.

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

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

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

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

  • 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.

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

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

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

  • 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.

将所有内容综合起来,你的代码应该如下所示:
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,真的很抱歉耽搁了 :/ 非常易于理解和绝对精彩的解决方案,它像魔法一样运作!非常感谢。 - Cyrille-Louis Contamine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接