如何在WriteJson期间删除属性

时间:2014-02-17 21:49:02

标签: c# json.net

我创建了一个测试控制台应用程序,它有一个名为Other的简单类。作为练习,我想在序列化时将OtherString属性设置为null。我知道如何使用自定义ContractResolver完成此操作。我需要能够使用自定义转换器执行此操作。

我写的第一个转换器很简单,我认为应该如此。但是,它会抛出“使用类型'JsonContractandConvert.Models.Other'检测到的自引用循环。路径''。”例外。做了一些阅读后,我做了一些改动,现在我有一个工作转换器。这些变化更加冗长,但也很有效。

我的问题是为什么需要进行这些更改,是否有更好的方法来使用转换器?

其他课程:

[JsonConverter(typeof(OtherConverter))]
public class Other
{
    public int Id { get; set; }
    public string OtherString { get; set; }
    public int OtherInt { get; set; }

public string OtherName
    {
        get
        {
        return "Other Name = " + this.OtherString;
        }
    }
}

第一次尝试:(这一次抛出异常)

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    if (!this.CanConvert(value.GetType())) return;

    var entity = value as Other;
    if (entity == null) return;

    entity.OtherString = null;

    serializer.Serialize(writer, entity);
}

第二次尝试:(这可以按预期工作)

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    if (!this.CanConvert(value.GetType())) return;

    var entity = value as Other;
    if (entity == null) return;

    entity.OtherString = null;

    writer.WriteStartObject();
    var props = entity.GetType().GetProperties();
    foreach (var propertyInfo in props)
    {
        var ignorAttribute =
            propertyInfo.CustomAttributes.FirstOrDefault(i => i.AttributeType == typeof(JsonIgnoreAttribute));

    if (ignorAttribute != null) continue;
                var tempVal = propertyInfo.GetValue(entity);
            if (tempVal == null) continue;

            writer.WritePropertyName(propertyInfo.Name);
            serializer.Serialize(writer, tempVal);
        }
    }
    writer.WriteEndObject();
}

编辑:

以下是我用来测试的控制台应用程序的代码。

class Program
{
    static void Main(string[] args)
    {
        var otherObj = new Other { Id = 123, OtherInt = 456, OtherString = "This is the other string"};
        var json = JsonConvert.SerializeObject(otherObj, Formatting.Indented);
        Console.WriteLine(json);
    }
}

1 个答案:

答案 0 :(得分:2)

回答我自己的问题......(我想我终于了解了Json Converters)

回答我的基本问题,为什么一种方式有效,另一种方式无效。我认为答案只是不起作用的方式是错误的。据我所知,你需要使用writer对象。如果你不这样做就行不通。 (有些情况可能并非如此,但我从未找到过。)

如果您只想从Json中删除属性,请使用[JsonIgnore]。 (简单吧?)

如果你想改变一些值,你需要使用转换器。通过使用转换器,您负责序列化转换器处理的对象。因此,如果对象是一个简单的类型,如String,或者Int,它真的很容易。如果它像数组或复杂对象那样更复杂,那么需要更多的计划。

这是一个简单的String Converter的例子:

要序列化的类:

public class Account
{
    public int Id { get; set; }
    public string AccountName { get; set; }

    [JsonIgnore]
    public virtual Account DefaultAssignTo { get; set; }
    public int? DefaultAssignToId { get; set; }

    [JsonIgnore]
    public virtual ICollection<Role> Roles { get; set; }

    [JsonIgnore]
    public virtual Other Other { get; set; }
    public int? OtherId { get; set; }

    [JsonConverter(typeof(StringConverter))]
    public string OtherName
    {
        get
        {
                return "Name = " + this.AccountName;
        }
    }

}

转换器:

public class StringConverter : JsonConverter
{        
    public override bool CanConvert(Type objectType)
    {
        return typeof(string).IsAssignableFrom(objectType);
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (!this.CanConvert(value.GetType())) return;

        writer.WriteValue("blah blah blah");    
    }
}

运行:(如您所见,我正在为我的数据使用Entity Framework)

using (var db = new Context())
{
    var json = JsonConvert.SerializeObject(db.Accounts.FirstOrDefault(), Formatting.Indented, 
        new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
    Console.WriteLine(json);
}

返回:

{
  "Id": 43,
  "AccountName": "John",
  "DefaultAssignToId": 43,
  "OtherId": 19,
  "OtherName": "blah blah blah"
}

让我搞砸了的一件事就是何时使用writer.WriteStartObject()。基本上,如果要转换复杂对象,则需要使用它。如果这样做,则需要创建所有属性名称和值。在OP中你可以看到我如何做到这一点的一个例子。最大的缺点是任何属性装饰的Json属性都不会自动发生。所以,如果可以的话,装饰类并让序列化器处理它。如果您使用错误,可能会遇到一个有趣的问题。

在String转换器中替换:

writer.WriteValue("blah blah blah");

使用:

writer.WriteStartObject();
writer.WritePropertyName("BlahProp");
serializer.Serialize(writer, "blah blah blah");
writer.WriteEndObject();

再次运行它,这是输出:(注意OtherName现在是一个对象而不是一个字符串)

{
  "Id": 43,
  "AccountName": "John",
  "DefaultAssignToId": 43,
  "OtherId": 19,
  "OtherName": {
    "BlahProp": "blah blah blah"
  }
}

Roles系列还需要一个转换器。首先要注意的两件事是,您不需要指定属性名称,其次,Role类所​​装饰的任何Json属性都将按预期工作。

using BaseCollection = System.Collections.Generic.ICollection<JsonContractandConvert.Models.Role>;
public class RemoveAccountsFromRolesConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(BaseCollection).IsAssignableFrom(objectType);
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (!this.CanConvert(value.GetType())) return;

        var entities = value as BaseCollection;
        if (entities == null) return;

        writer.WriteStartArray();
        foreach (var entity in entities)
        {
        entity.Accounts = null;
        serializer.Serialize(writer, entity);
        }
        writer.WriteEndArray();
    }
}

最后,这是我为我的真实项目创建的几个转换器:

这个将对象转换为浅拷贝。如果您有一个集合,其中包含的集合可能会导致自引用循环,那么这很有用。

public class ShallowCopyCollectionConverter<TCollectionType, TCopyType> : JsonConverter
    where TCollectionType : IEnumerable<TbdEntity>
    where TCopyType : TbdEntity, new()
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(TCollectionType).IsAssignableFrom(objectType);
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (this.CanConvert(value.GetType()) == false) return;

        var entities = (TCollectionType)value;
        writer.WriteStartArray();
        foreach (var entity in entities)
        {
            serializer.Serialize(writer, entity.ShallowCopy<TCopyType>()); //ShallowCopy<> is a method in the base class that all of my classes extend.
        }
        writer.WriteEndArray();
    }
}

这个将使用具体类型转换摘要:

public class DataSnapInConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(DataSnapIn) == (objectType);
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (this.CanConvert(objectType) == false) return null;

        var jo = JObject.Load(reader);
        var typeName = jo["snapInType"] ?? jo["SnapInType"];  //the abstract classes have this property to identify what concrete class they are.
        var typeNameString = typeName.ToString();

        var deserializeType = Type.GetType(typeNameString);
        if(deserializeType == null)
            throw new Exception("SnapInType is null or does not reference a valid class.");

        var result = Activator.CreateInstance(deserializeType);
        serializer.Populate(jo.CreateReader(), result);

        return result;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}