在JSON.NET中为反序列化转换接口

tme*_*ser 116 .net c# json json.net

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

public IThingy Thing
Run Code Online (Sandbox Code Playgroud)

会产生错误:

无法创建IThingy类型的实例.Type是接口或抽象类,无法实例化.

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

我一直在研究JSON.NET的文档已有一段时间了,我在这个网站上找到的与此相关的问题都来自一年多以前.有帮助吗?

此外,如果重要,我的应用程序是用.NET 4.0编写的.

Mar*_*uer 102

@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; }
}
Run Code Online (Sandbox Code Playgroud)

  • 这根本不好.使用接口的关键是使用依赖注入,但是使用构造函数所需的对象类型参数执行此操作时,您完全搞砸了将接口作为属性. (21认同)
  • 如何使用ICollection?ICollection <IGuest>来宾{get; set;} (13认同)
  • 它适用于ICollection <ConcreteClass>,因此ICollection <Guest>可以工作.就像一个FYI,你可以将属性[JsonConstructor]放在你的构造函数上,这样如果你碰巧有多个构造函数它会默认使用它 (12认同)
  • 如果你有多个构造函数,你可以使用`[JsonConstructor]`属性标记你的特殊构造函数. (9认同)
  • 我遇到了同样的问题,在我的情况下我有几个接口的实现(在你的例子中接口是ILocation)所以如果有像MyLocation,VIPLocation,OrdinaryLocation这样的类.如何将这些映射到Location属性?如果您只有一个像MyLocation这样的实现很简单,但如果有多个ILocation实现,该如何做? (6认同)
  • @MarkMeuer - 不仅仅是 DI。接口促进实现之间的松散耦合,允许使用许多不同的实现。ILocation 可以解析为餐厅、星球、街道地址或许多其他类型;通过限制构造函数中的类型,我们失去了这些好处。理想的是能够将接口反序列化为具体类型*而无需*提前知道它是什么,因为有时我们就是不知道。 (3认同)
  • @JérômeMEVEL +1 为您 -1 为答案。让我惊讶的是有多少人会做这样的事情。 (2认同)
  • @MarkMeuer 拥有引用具体类型的构造函数的另一个问题是,您需要能够引用包含这些具体类型的程序集。如果您在单独的程序集中定义了接口以避免这种耦合,则这会消除这种耦合。 (2认同)

Ste*_*rex 50

(从这个问题复制)

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

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

这只是使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我非常喜欢这种方法并将其应用到我们自己的项目中.我甚至添加了一个`ConcreteListTypeConverter <TInterface,TImplementation>`来处理类型为`IList <TInterface>`的类成员. (10认同)
  • 这是一个很好的代码.尽管如此,在问题中使用"concreteTypeConverter"的实际代码可能更好. (3认同)
  • @Oliver - 你能发布你的`ConcreteListTypeConverter <TInterface,TImplementation>`实现吗? (2认同)
  • 而且,如果您有ISomething的两个实现者? (2认同)

Maf*_*fii 43

为什么要使用转换器?有一个本机功能Newtonsoft.Json来解决这个确切的问题:

设置TypeNameHandlingJsonSerializerSettingsTypeNameHandling.Auto

JsonConvert.SerializeObject(
        toSerialize,
        new JsonSerializerSettings()
        {
          TypeNameHandling = TypeNameHandling.Auto
        });
Run Code Online (Sandbox Code Playgroud)

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

我测试了它,它就像一个魅力,即使有列表.

来源和替代手动实施:代码内部博客

  • 完美,这帮助我进行了快速而肮脏的深度克隆(http://stackoverflow.com/questions/78536/deep-cloning-objects) (3认同)
  • 我只是在反序列化上尝试了这个并且它不起作用.Stack Overflow问题的主题是"在JSON.NET中为反序列化生成接口" (3认同)
  • @JustinRusso它仅在使用相同设置序列化json时有效 (2认同)
  • Upvote用于快速(如果不是脏的)解决方案.如果您只是序列化配置,这是有效的.击败停止开发建立转换器,当然击败装饰每个注入的财产.serializer.TypeNameHandling = TypeNameHandling.Auto; JsonConvert.DefaultSettings().TypeNameHandling = TypeNameHandling.Auto; (2认同)
  • @Mafii 在我更改为 TypeNameHandling.All 之前它对我不起作用。我看到序列化的字符串不包含自动设置的类型信息。这与反序列化无关。我正在序列化接口对象 (2认同)
  • @KyleDelaney AFAIU:1)此类攻击主要针对标准库中的类型,该库执行一些动态代码。2) 除了 typename 之外,JSON 内容还具有构造函数参数,该参数将传递给易受攻击的类进行解释。有关示例,请参阅[此问答](/sf/ask/2769616811/)。 (2认同)

Eri*_*dil 36

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

Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Converters.Add(new DTOJsonConverter());
Interfaces.IEntity entity = serializer.Deserialize(jsonReader);
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

只有解串器才需要DTOJsonConverter.序列化过程保持不变.Json对象不需要嵌入具体类型名称.

这个SO帖子提供了与通用JsonConverter相同的解决方案.

  • 为什么你比较`FullName`s时,你可能只是直接比较类型? (2认同)

Gil*_*dor 14

使用此类,将抽象类型映射到实际类型:

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);
}
Run Code Online (Sandbox Code Playgroud)

...当反序列化时:

        var settings = new JsonSerializerSettings
        {
            Converters = {
                new AbstractConverter<Thing, IThingy>(),
                new AbstractConverter<Thing2, IThingy2>()
            },
        };

        JsonConvert.DeserializeObject(json, type, settings);
Run Code Online (Sandbox Code Playgroud)

  • 值得把它放到转换器类声明中:`其中TReal:TAbstract`以确保它可以转换为类型 (3认同)
  • 更完整的 where 可能是 `where TReal : class, TAbstract, new()`。 (2认同)
  • 我也将这个转换器与 struct 一起使用,我相信“where TReal:TAbstract”就足够了。谢谢大家。 (2认同)
  • 金!光滑的路要走。 (2认同)
  • 伟大的。与已接受的答案相反,如果您无法修改实际的类并且需要自己实现接口,则此解决方案也适用。 (2认同)

小智 8

对于那些可能对 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我对重写的`CanConvert(Type objectType) { return true;}` 感到困惑。看起来很hacky,这到底有什么帮助?我可能错了,但这不就像告诉一个没有经验的小战士,无论对手如何,他们都会赢得战斗吗? (2认同)

Sea*_*sey 6

任何对象都不会 IThingy,因为接口根据定义都是抽象的。

首先序列化的对象是某种具体类型,实现了抽象接口。您需要让同一个具体类恢复序列化数据。

生成的对象将是某种实现正在寻找的抽象接口的类型。

文档中可以看出,您可以使用

(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));
Run Code Online (Sandbox Code Playgroud)

反序列化时通知 JSON.NET 有关具体类型的信息。


mcw*_*933 6

你可能会尝试两件事:

实现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();
  }
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您可以在对象模型中执行此操作,请在IPerson和叶对象之间实现具体的基类,并对其进行反序列化.

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


smi*_*rth 6

我发现这很有用.你可能也是.

示例用法

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

定制创作转换器

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

Json.NET文档

  • 这不是一个可行的解决方案。不处理列表并导致到处都是装饰器/注释。 (2认同)

A. *_*rel 6

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#"
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用自定义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;
    }
}
Run Code Online (Sandbox Code Playgroud)

您将需要使用JsonConverter属性装饰“ Profession”属性,以使其知道使用自定义转换器:

    public class Person
    {
        [JsonConverter(typeof(ProfessionConverter))]
        public IProfession Profession { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用接口强制转换类:

Person person = JsonConvert.DeserializeObject<Person>(jsonString);
Run Code Online (Sandbox Code Playgroud)