JSON.NET中用于反序列化的强制转换接口



我正在尝试建立一个阅读器,它将从各种网站(想想信息抓取)中获取JSON对象并将其转换为c#对象。我目前使用JSON。. NET用于反序列化过程。我遇到的问题是,它不知道如何处理类中的接口级属性。所以一些自然的东西:

public IThingy Thing

将产生错误:

无法创建类型为IThingy的实例。类型是接口或抽象类,不能实例化。

相对重要的是让它是一个Thingy而不是一个Thingy,因为我正在工作的代码被认为是敏感的,单元测试是非常重要的。对于像Thingy这样完全成熟的对象来说,为原子测试脚本模拟对象是不可能的。它们必须是接口。

我一直在研究JSON。NET的文档已经有一段时间了,我在这个网站上找到的与此相关的问题都是一年前的。任何帮助吗?

另外,如果有关系的话,我的应用程序是用。net 4.0编写的。

@SamualDavis在一个相关问题中提供了一个很好的解决方案,我将在这里总结一下。

如果你必须将JSON流反序列化为具有接口属性的具体类,你可以将具体类作为参数包含到该类的构造函数中! NewtonSoft反序列化器足够聪明,可以发现它需要使用那些具体的类来反序列化属性。

下面是一个例子:

public class Visit : IVisit
{
    /// <summary>
    /// This constructor is required for the JSON deserializer to be able
    /// to identify concrete classes to use when deserializing the interface properties.
    /// </summary>
    public Visit(MyLocation location, Guest guest)
    {
        Location = location;
        Guest = guest;
    }
    public long VisitId { get; set; }
    public ILocation Location { get;  set; }
    public DateTime VisitDate { get; set; }
    public IGuest Guest { get; set; }
}

为什么使用转换器?Newtonsoft.Json中有一个原生功能可以解决这个问题:

将"JsonSerializerSettings"中的"TypeNameHandling"设置为"TypeNameHandling.Auto"

JsonConvert.SerializeObject(
  toSerialize,
  new JsonSerializerSettings()
  {
    TypeNameHandling = TypeNameHandling.Auto
  });

这将把每个类型放入json中,而不是作为类型的具体实例,而是作为接口或抽象类。

确保对序列化和反序列化使用相同的设置。

我测试了一下,它就像一个魅力,即使是列表。

搜索结果带有站点链接的Web结果

<<p>⚠️警告/strong>:

仅对来自已知且可信来源的json使用此选项。用户snipsnipclip正确地提到这确实是一个漏洞。

更多信息参见CA2328和SCS0028。


源代码和替代的手动实现:Code Inside Blog

(从这个问题复制)

在我无法控制传入JSON的情况下(因此不能确保它包含$type属性),我编写了一个自定义转换器,只允许您显式指定具体类型:

public class Model
{
    [JsonConverter(typeof(ConcreteTypeConverter<Something>))]
    public ISomething TheThing { get; set; }
}

这只是使用Json的默认序列化器实现。. Net,同时显式指定具体类型。

在这篇博客文章中有一个概述。源代码如下:

public class ConcreteTypeConverter<TConcrete> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        //assume we can convert to anything for now
        return true;
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //explicitly specify the concrete type we want to create
        return serializer.Deserialize<TConcrete>(reader);
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //use the default serialization - it works fine
        serializer.Serialize(writer, value);
    }
}

使用这个类将抽象类型映射为实类型:

public class AbstractConverter<TReal, TAbstract> 
    : JsonConverter where TReal : TAbstract
{
    public override Boolean CanConvert(Type objectType)
        => objectType == typeof(TAbstract);
    public override Object ReadJson(JsonReader reader, Type type, Object value, JsonSerializer jser)
        => jser.Deserialize<TReal>(reader);
    public override void WriteJson(JsonWriter writer, Object value, JsonSerializer jser)
        => jser.Serialize(writer, value);
}

和反序列化时:

var settings = new JsonSerializerSettings
{
    Converters = {
        new AbstractConverter<Thing, IThingy>(),
        new AbstractConverter<Thing2, IThingy2>()
    },
};
JsonConvert.DeserializeObject(json, type, settings);

要启用多个接口实现的反序列化,您可以使用JsonConverter,但不能通过属性:

Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Converters.Add(new DTOJsonConverter());
Interfaces.IEntity entity = serializer.Deserialize(jsonReader);

DTOJsonConverter用一个具体的实现映射每个接口:

class DTOJsonConverter : Newtonsoft.Json.JsonConverter
{
    private static readonly string ISCALAR_FULLNAME = typeof(Interfaces.IScalar).FullName;
    private static readonly string IENTITY_FULLNAME = typeof(Interfaces.IEntity).FullName;

    public override bool CanConvert(Type objectType)
    {
        if (objectType.FullName == ISCALAR_FULLNAME
            || objectType.FullName == IENTITY_FULLNAME)
        {
            return true;
        }
        return false;
    }
    public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        if (objectType.FullName == ISCALAR_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientScalar));
        else if (objectType.FullName == IENTITY_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientEntity));
        throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
    }
    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

DTOJsonConverter仅对反序列化器是必需的。序列化过程不变。Json对象不需要嵌入具体的类型名。

这篇文章提供了一个通用的JsonConverter进一步相同的解决方案。

Nicholas Westby在一篇很棒的文章中提供了一个很好的解决方案。

如果您想将JSON反序列化为实现这样一个接口的许多可能的类之一:

public class Person
{
    public IProfession Profession { get; set; }
}
public interface IProfession
{
    string JobTitle { get; }
}
public class Programming : IProfession
{
    public string JobTitle => "Software Developer";
    public string FavoriteLanguage { get; set; }
}
public class Writing : IProfession
{
    public string JobTitle => "Copywriter";
    public string FavoriteWord { get; set; }
}
public class Samples
{
    public static Person GetProgrammer()
    {
        return new Person()
        {
            Profession = new Programming()
            {
                FavoriteLanguage = "C#"
            }
        };
    }
}

您可以使用自定义JSON转换器:

public class ProfessionConverter : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(IProfession);
    }
    public override void WriteJson(JsonWriter writer,
        object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }
    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var profession = default(IProfession);
        switch (jsonObject["JobTitle"].Value())
        {
            case "Software Developer":
                profession = new Programming();
                break;
            case "Copywriter":
                profession = new Writing();
                break;
        }
        serializer.Populate(jsonObject.CreateReader(), profession);
        return profession;
    }
}

你需要用JsonConverter属性来装饰"Profession"属性,让它知道使用你的自定义转换器:

    public class Person
    {
        [JsonConverter(typeof(ProfessionConverter))]
        public IProfession Profession { get; set; }
    }

然后,你可以用一个接口强制转换你的类:

Person person = JsonConvert.DeserializeObject<Person>(jsonString);

我发现这很有用。你可能也会。

public class Parent
{
    [JsonConverter(typeof(InterfaceConverter<IChildModel, ChildModel>))]
    IChildModel Child { get; set; }
}

自定义创建转换器

public class InterfaceConverter<TInterface, TConcrete> : CustomCreationConverter<TInterface>
    where TConcrete : TInterface, new()
{
    public override TInterface Create(Type objectType)
    {
        return new TConcrete();
    }
}

Json。. NET文档

您可以尝试以下两种方法:

实现一个try/parse模型:

public class Organisation {
  public string Name { get; set; }
  [JsonConverter(typeof(RichDudeConverter))]
  public IPerson Owner { get; set; }
}
public interface IPerson {
  string Name { get; set; }
}
public class Tycoon : IPerson {
  public string Name { get; set; }
}
public class Magnate : IPerson {
  public string Name { get; set; }
  public string IndustryName { get; set; }
}
public class Heir: IPerson {
  public string Name { get; set; }
  public IPerson Benefactor { get; set; }
}
public class RichDudeConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return (objectType == typeof(IPerson));
  }
  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    // pseudo-code
    object richDude = serializer.Deserialize<Heir>(reader);
    if (richDude == null)
    {
        richDude = serializer.Deserialize<Magnate>(reader);
    }
    if (richDude == null)
    {
        richDude = serializer.Deserialize<Tycoon>(reader);
    }
    return richDude;
  }
  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

或者,如果可以在对象模型中这样做,则在IPerson和叶子对象之间实现一个具体的基类,并对其进行反序列化。

第一种方法可能在运行时失败,第二种方法需要更改对象模型并将输出均匀化到最小公分母。

对于那些可能对Oliver引用的ConcreteListTypeConverter感到好奇的人,这里是我的尝试:

public class ConcreteListTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface 
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var res = serializer.Deserialize<List<TImplementation>>(reader);
        return res.ConvertAll(x => (TInterface) x);
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

任何对象都不能,因为接口在定义上都是抽象的

第一次序列化的对象是某种具体的类型,实现了抽象的接口。您需要使用相同的具体类来恢复序列化的数据。

结果对象将是某种类型的实现你正在寻找的抽象接口。

根据文档,您可以使用

(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));

在反序列化时通知JSON。NET关于具体类型。

不管怎样,我最终不得不自己处理这个问题。每个对象都有一个反序列化(string jsonStream)方法。其中的一些片段:

JObject parsedJson = this.ParseJson(jsonStream);
object thingyObjectJson = (object)parsedJson["thing"];
this.Thing = new Thingy(Convert.ToString(thingyObjectJson));

在本例中,new Thingy(string)是一个构造函数,它将调用相应具体类型的Deserialize(string jsonStream)方法。这个方案将继续向下,向下,直到你得到的基点json。. NET可以处理。

this.Name = (string)parsedJson["name"];
this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);

等等。这个设置允许我给出json。它可以处理。NET设置,而不必重构库本身的大部分,也不必使用笨拙的try/parse模型,因为涉及的对象数量太多,这些模型会使整个库陷入困境。这也意味着我可以有效地处理特定对象上的任何json更改,并且我不需要担心该对象所涉及的所有内容。这绝不是理想的解决方案,但从我们的单元和集成测试来看,它运行得相当好。

假设有如下自动设置:

public class AutofacContractResolver : DefaultContractResolver
{
    private readonly IContainer _container;
    public AutofacContractResolver(IContainer container)
    {
        _container = container;
    }
    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);
        // use Autofac to create types that have been registered with it
        if (_container.IsRegistered(objectType))
        {
           contract.DefaultCreator = () => _container.Resolve(objectType);
        }  
        return contract;
    }
}

然后,假设你的类是这样的:

public class TaskController
{
    private readonly ITaskRepository _repository;
    private readonly ILogger _logger;
    public TaskController(ITaskRepository repository, ILogger logger)
    {
        _repository = repository;
        _logger = logger;
    }
    public ITaskRepository Repository
    {
        get { return _repository; }
    }
    public ILogger Logger
    {
        get { return _logger; }
    }
}

因此,解析器在反序列化中的用法如下:

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<TaskRepository>().As<ITaskRepository>();
builder.RegisterType<TaskController>();
builder.Register(c => new LogService(new DateTime(2000, 12, 12))).As<ILogger>();
IContainer container = builder.Build();
AutofacContractResolver contractResolver = new AutofacContractResolver(container);
string json = @"{
      'Logger': {
        'Level':'Debug'
      }
}";
// ITaskRespository and ILogger constructor parameters are injected by Autofac 
TaskController controller = JsonConvert.DeserializeObject<TaskController>(json, new JsonSerializerSettings
{
    ContractResolver = contractResolver
});
Console.WriteLine(controller.Repository.GetType().Name);

你可以看到更多的细节在http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm

我的解决方案,我喜欢,因为它是很好的通用,是如下:

/// <summary>
/// Automagically convert known interfaces to (specific) concrete classes on deserialisation
/// </summary>
public class WithMocksJsonConverter : JsonConverter
{
    /// <summary>
    /// The interfaces I know how to instantiate mapped to the classes with which I shall instantiate them, as a Dictionary.
    /// </summary>
    private readonly Dictionary<Type,Type> conversions = new Dictionary<Type,Type>() { 
        { typeof(IOne), typeof(MockOne) },
        { typeof(ITwo), typeof(MockTwo) },
        { typeof(IThree), typeof(MockThree) },
        { typeof(IFour), typeof(MockFour) }
    };
    /// <summary>
    /// Can I convert an object of this type?
    /// </summary>
    /// <param name="objectType">The type under consideration</param>
    /// <returns>True if I can convert the type under consideration, else false.</returns>
    public override bool CanConvert(Type objectType)
    {
        return conversions.Keys.Contains(objectType);
    }
    /// <summary>
    /// Attempt to read an object of the specified type from this reader.
    /// </summary>
    /// <param name="reader">The reader from which I read.</param>
    /// <param name="objectType">The type of object I'm trying to read, anticipated to be one I can convert.</param>
    /// <param name="existingValue">The existing value of the object being read.</param>
    /// <param name="serializer">The serializer invoking this request.</param>
    /// <returns>An object of the type into which I convert the specified objectType.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return serializer.Deserialize(reader, this.conversions[objectType]);
        }
        catch (Exception)
        {
            throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
        }
    }
    /// <summary>
    /// Not yet implemented.
    /// </summary>
    /// <param name="writer">The writer to which I would write.</param>
    /// <param name="value">The value I am attempting to write.</param>
    /// <param name="serializer">the serializer invoking this request.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

}

你可以通过添加一个接受Dictionary<type,>类型参数的构造函数,明显而简单地将其转换为更通用的转换器。用它实例化转换实例变量。

几年过去了,我也遇到了类似的问题。在我的例子中,有大量嵌套的接口,并且倾向于在运行时生成具体的类,以便它可以与泛型类一起工作。

我决定在运行时创建一个代理类来包装Newtonsoft返回的对象。

这种方法的优点是它不需要类的具体实现,可以自动处理任何深度的嵌套接口。你可以在我的博客上看到更多。

using Castle.DynamicProxy;
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;
namespace LL.Utilities.Std.Json
{
    public static class JObjectExtension
    {
        private static ProxyGenerator _generator = new ProxyGenerator();
        public static dynamic toProxy(this JObject targetObject, Type interfaceType) 
        {
            return _generator.CreateInterfaceProxyWithoutTarget(interfaceType, new JObjectInterceptor(targetObject));
        }
        public static InterfaceType toProxy<InterfaceType>(this JObject targetObject)
        {
            return toProxy(targetObject, typeof(InterfaceType));
        }
    }
    [Serializable]
    public class JObjectInterceptor : IInterceptor
    {
        private JObject _target;
        public JObjectInterceptor(JObject target)
        {
            _target = target;
        }
        public void Intercept(IInvocation invocation)
        {
            var methodName = invocation.Method.Name;
            if(invocation.Method.IsSpecialName && methodName.StartsWith("get_"))
            {
                var returnType = invocation.Method.ReturnType;
                methodName = methodName.Substring(4);
                if (_target == null || _target[methodName] == null)
                {
                    if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                    {
                        invocation.ReturnValue = null;
                        return;
                    }
                }
                if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                {
                    invocation.ReturnValue = _target[methodName].ToObject(returnType);
                }
                else
                {
                    invocation.ReturnValue = ((JObject)_target[methodName]).toProxy(returnType);
                }
            }
            else
            {
                throw new NotImplementedException("Only get accessors are implemented in proxy");
            }
        }
    }

}

用法:

var jObj = JObject.Parse(input);
InterfaceType proxyObject = jObj.toProxy<InterfaceType>();

使用这个JsonKnownTypes,这是非常相似的使用方式,它只是添加了discriminator到json:

[JsonConverter(typeof(JsonKnownTypeConverter<Interface1>))]
[JsonKnownType(typeof(MyClass), "myClass")]
public interface Interface1
{  }
public class MyClass : Interface1
{
    public string Something;
}

现在当你在json中序列化对象时将添加"$type""myClass"值它将用于反序列化

Json:

{"Something":"something", "$type":"derived"}

我的解决方案是在构造函数中添加接口元素。

public class Customer: ICustomer{
     public Customer(Details details){
          Details = details;
     }
     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {get; set;}
}

您还可以使用自定义texttinputformatter,不需要外部库,还可以帮助您了解如何处理(反)序列化任何类型的数据

public class MyInputTypeFormatter : TextInputFormatter
{
    public MyInputTypeFormatter()
    {
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
        SupportedEncodings.Add(Encoding.UTF8);
    }

    protected override bool CanReadType(Type type)
    {
        return type == typeof(MyClass);
    }
    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
    {
        var httpContext = context.HttpContext;
        var serviceProvider = httpContext.RequestServices;
        var logger = serviceProvider.GetRequiredService<ILogger<ImageTypeConverter>>();
        using var reader = new StreamReader(httpContext.Request.Body, encoding);
        {
            var data = await reader.ReadToEndAsync();
            if (data.Contains("Hello"))
            {
                var myClass= new MyClass(data);
                return await InputFormatterResult.SuccessAsync(myClass);
            }
            else
            {
                return await InputFormatterResult.FailureAsync();
            }
        }

    }
}

然后,使用

将这个输入格式化器添加到输入格式化器列表中
services.AddControllers(options=> {
            options.InputFormatters.Insert(0, new MyInputFormatter());
        });

0表示这是模型绑定时调用的第一个输入格式化器。

看起来工作量很大,但大部分只是样板文件。我将解释这是如何工作的,

你有一个动作方法/路由,它有一个MyClass类型的参数。当一个请求来到它,你的输入格式化器的CanReadType被调用,它返回true意味着它将处理反序列化。然后调用ReadRequestBodyAsync方法,并将请求数据提供给它。

您可以对数据做任何您想做的事情,如果反序列化成功,则返回MyClass类型的对象。否则就返回失败。

在反序列化中可以使用

using (JsonDocument document = JsonDocument.Parse(jsonString))
{
JsonElement root = document.RootElement;
// ...
}

当输入被解析成json对象,然后保存到DOM中时,你可以遍历元素。然后,您可以查看它们包含的内容,并使用它们的数据手动创建类,并将输入即接口转换为类。

注意:JsonDocument是在。net 3.1中引入的你可以在这里查看如何使用它

关于如何使用texttinputformatter和TextOutputFormatter的更多信息使用自定义输入格式化器的好处是,它提供了一个中心类来处理可能使用多个接口的自定义类。它还使您可以很好地控制对输入数据的处理。

相关内容

  • 没有找到相关文章

最新更新