我创建了一个测试控制台应用程序,它有一个名为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);
}
}
答案 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();
}
}