使用 Json.net 仅将接口属性序列化为 JSON

问题描述 投票:0回答:10

使用像这样的简单类/接口

public interface IThing
{
    string Name { get; set; }
}

public class Thing : IThing
{
    public int Id { get; set; }
    public string Name { get; set; }
}

如何获取只有“Name”属性(只有底层接口的属性)的JSON字符串?

事实上,当我这样做时:

var serialized = JsonConvert.SerializeObject((IThing)theObjToSerialize, Formatting.Indented);
Console.WriteLine(serialized);

我得到了 JSON 格式的完整对象(Id + Name);

c# serialization json.net
10个回答
28
投票

我用的方法,

public class InterfaceContractResolver : DefaultContractResolver
{
    private readonly Type _InterfaceType;
    public InterfaceContractResolver (Type InterfaceType)
    {
        _InterfaceType = InterfaceType;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        //IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
        IList<JsonProperty> properties = base.CreateProperties(_InterfaceType, memberSerialization);
        return properties;
    }
}

// To serialize do this:
var settings = new JsonSerializerSettings() {
     ContractResolver = new InterfaceContractResolver (typeof(IThing))
};
string json = JsonConvert.SerializeObject(theObjToSerialize, settings);
                 
                            

23
投票

带有嵌套接口的改进版本+对xsd.exe对象的支持

这里还有另一种变化。该代码来自 http://www.tomdupont.net/2015/09/how-to-only-serialize-interface.html 与此处的其他答案相比有以下改进

  • 处理层次结构,因此如果
    Interface2[]
    中包含
    Interface1
    ,那么它将被序列化。
  • 我尝试序列化 WCF 代理对象,生成的 JSON 为

    {}
    。结果所有属性都设置为
    Ignore=true
    所以我必须添加一个循环来将它们全部设置为不被忽略。

    public class InterfaceContractResolver : DefaultContractResolver
    {
        private readonly Type[] _interfaceTypes;
    
        private readonly ConcurrentDictionary<Type, Type> _typeToSerializeMap;
    
        public InterfaceContractResolver(params Type[] interfaceTypes)
        {
            _interfaceTypes = interfaceTypes;
    
            _typeToSerializeMap = new ConcurrentDictionary<Type, Type>();
        }
    
        protected override IList<JsonProperty> CreateProperties(
            Type type,
            MemberSerialization memberSerialization)
        {
            var typeToSerialize = _typeToSerializeMap.GetOrAdd(
                type,
                t => _interfaceTypes.FirstOrDefault(
                    it => it.IsAssignableFrom(t)) ?? t);
    
            var props = base.CreateProperties(typeToSerialize, memberSerialization);
    
            // mark all props as not ignored
            foreach (var prop in props)
            {
                prop.Ignored = false;
            }
    
            return props;
        }
    }
    

22
投票

受到@user3161686的启发,这里有一个小修改

InterfaceContractResolver

public class InterfaceContractResolver<TInterface> : DefaultContractResolver where TInterface : class
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> properties = base.CreateProperties(typeof(TInterface), memberSerialization);
        return properties;
    }
}

12
投票

您可以使用条件序列化。看看这个链接。基本上,您需要实现

IContractResolver
接口,重载
ShouldSerialize
方法并将解析器传递给 Json Serializer 的构造函数。


10
投票

[JsonIgnore]
的替代方案是
[DataContract]
[DataMember]
属性。如果您的类带有
[DataContract]
标记,则序列化程序将仅处理带有
[DataMember]
属性标记的属性(
JsonIgnore
是“选择退出”模型,而
DataContract
是“选择加入”)。

[DataContract]
public class Thing : IThing
{
    [DataMember]
    public int Id { get; set; }

    public string Name { get; set; }
}

这两种方法的局限性在于它们必须在类中实现,不能将它们添加到接口定义中。


7
投票

您可以添加

[JsonIgnore]
注释来忽略属性。


4
投票

我想分享一下我们在面对这项任务时最终做了什么。给定 OP 的接口和类...

public interface IThing
{
    string Name { get; set; }
}

public class Thing : IThing
{
   public int Id { get; set; }
   public string Name { get; set; }
}

...我们创建了一个直接实现接口的类...

public class DirectThing : IThing
{
   public string Name { get; set; }
}

然后简单地序列化我们的

Thing
实例,将其反序列化为
DirectThing
,然后将其序列化为
DirectThing
:

var thing = new Thing();
JsonConvert.SerializeObject(
    JsonConvert.DeserializeObject<DirectThing>(JsonConvert.SerializeObject(thing)));

这种方法可以使用长接口继承链...您只需要在感兴趣的级别创建一个直接类(在本例中为

DirectThing
)。无需担心反射或属性。

从维护的角度来看,如果您将成员添加到

DirectThing
中,
IThing
类很容易维护,因为如果您没有将它们也放入
DirectThing
中,编译器会给出错误。但是,如果您从 IThing
删除成员 X 并将其放入
Thing
中,那么您必须记住将其从
DirectThing
中删除,否则 X 将出现在最终结果中。

从性能角度来看,这里发生了三个(反)序列化操作,而不是一个,因此根据您的情况,您可能希望评估基于反射器/属性的解决方案与此解决方案的性能差异。就我而言,我只是小规模地这样做,所以我并不担心一些微/毫秒的潜在损失。

希望对某人有帮助!


3
投票

除了@monrow给出的答案之外,您还可以使用默认的[DataContract]和[DataMember] 看看这个

http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size.aspx


2
投票

最后我知道什么时候它不起作用...... 如果你想拥有另一个复杂的对象,它将无法正确序列化。

所以我制作了一个版本,它将仅提取存储在特定程序集中的数据以及具有相同基本接口的类型。

因此它被制作为.Net Core JsonContractResolver。

除了数据提取之外,它还解决:
a) 向客户端发送数据之前进行驼峰命名法转换
b) 使用允许范围内的最顶层接口(通过程序集) c) 修复字段顺序:大多数基类的字段将首先列出,嵌套对象也将满足此规则。

public class OutputJsonResolver : DefaultContractResolver
{
    #region Static Members
    private static readonly object syncTargets = new object();
    private static readonly Dictionary<Type, IList<JsonProperty>> Targets = new Dictionary<Type, IList<JsonProperty>>();

    private static readonly Assembly CommonAssembly = typeof(ICommon).Assembly;
    #endregion

    #region Override Members
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        if (type.Assembly != OutputJsonResolver.CommonAssembly)
            return base.CreateProperties(type, memberSerialization);

        IList<JsonProperty> properties;
        if (OutputJsonResolver.Targets.TryGetValue(type, out properties) == false)
        {
            lock (OutputJsonResolver.syncTargets)
            {
                if (OutputJsonResolver.Targets.ContainsKey(type) == false)
                {
                    properties = this.CreateCustomProperties(type, memberSerialization);

                    OutputJsonResolver.Targets[type] = properties;
                }
            }
        }

        return properties;
    }
    protected override string ResolvePropertyName(string propertyName)
    {
        return propertyName.ToCase(Casing.Camel);
    }
    #endregion

    #region Assistants
    private IList<JsonProperty> CreateCustomProperties(Type type, MemberSerialization memberSerialization)
    {
        // Hierarchy
        IReadOnlyList<Type> types = this.GetTypes(type);

        // Head
        Type head = types.OrderByDescending(item => item.GetInterfaces().Length).FirstOrDefault();

        // Sources
        IList<JsonProperty> sources = base.CreateProperties(head, memberSerialization);

        // Targets
        IList<JsonProperty> targets = new List<JsonProperty>(sources.Count);

        // Repository
        IReadOnlyDistribution<Type, JsonProperty> repository = sources.ToDistribution(item => item.DeclaringType);

        foreach (Type current in types.Reverse())
        {
            IReadOnlyPage<JsonProperty> page;
            if (repository.TryGetValue(current, out page) == true)
                targets.AddRange(page);
        }

        return targets;
    }
    private IReadOnlyList<Type> GetTypes(Type type)
    {
        List<Type> types = new List<Type>();

        if (type.IsInterface == true)
            types.Add(type);

        types.AddRange(type.GetInterfaces());

        return types;
    }
    #endregion
}

0
投票

基于现有答案的另一个答案。

@rasx 方法的小问题是,当序列化类型包含另一种复合类型的字段时实现接口,你会得到一个异常。

原因是

CreateProperties
是为每个序列化类型调用的,包括实际的序列化对象类型及其所有属性的类型。

解决方法是检查

CreateProperties
中检查的类型实际上是否是实现接口的类型或任何其他类型。

public class InterfaceContractResolver<TInterface> : DefaultContractResolver where TInterface : class
{
    protected override IList<JsonProperty> CreateProperties( Type type, MemberSerialization memberSerialization )
    {
        if ( typeof( TInterface ).IsAssignableFrom( type ) )
        {
            IList<JsonProperty> properties = base.CreateProperties(typeof(TInterface), memberSerialization);
            return properties;
        }
        else
        {
            return base.CreateProperties( type, memberSerialization );
        }
    }
}

看一下以前失败但现在有效的示例:

public interface IFoo
{
    string Bar { get; set; }

    Child Child { get; set; }
}

// this is the serialized type, it implements the interface
public class Foo : IFoo
{
    public string Bar { get; set; }
    public string Qux { get; set; }
    public Child Child { get; set; }
}

// this is a child type, doesn't implement the interface
public class Child
{
    public string Name { get; set; }
}

现在可以了

var foo = new Foo()
{
    Bar = "bar",
    Qux = "qux",
    Child = new Child()
    {
        Name = "child"
    }
};

// To serialize do this:
var settings = new JsonSerializerSettings()
{
    ContractResolver = new InterfaceContractResolver<IFoo>()
};

var s_foo = JsonConvert.SerializeObject(foo, settings);
var s_full = JsonConvert.SerializeObject(foo);

var foo_full = JsonConvert.DeserializeObject<Foo>(s_full);
var foo_foo = JsonConvert.DeserializeObject<Foo>(s_full, settings);

Console.WriteLine( foo_full );
Console.WriteLine( foo_foo );
© www.soinside.com 2019 - 2024. All rights reserved.