如何在 JSON.NET 中实现自定义 JsonConverter?
Posted
技术标签:
【中文标题】如何在 JSON.NET 中实现自定义 JsonConverter?【英文标题】:How to implement custom JsonConverter in JSON.NET? 【发布时间】:2011-12-23 06:02:32 【问题描述】:我正在尝试扩展此处给出的 JSON.net 示例 http://james.newtonking.com/projects/json/help/CustomCreationConverter.html
我有另一个从基类/接口派生的子类
public class Person
public string FirstName get; set;
public string LastName get; set;
public class Employee : Person
public string Department get; set;
public string JobTitle get; set;
public class Artist : Person
public string Skill get; set;
List<Person> people = new List<Person>
new Employee(),
new Employee(),
new Artist(),
;
我如何将跟随 Json 反序列化回列表
[
"Department": "Department1",
"JobTitle": "JobTitle1",
"FirstName": "FirstName1",
"LastName": "LastName1"
,
"Department": "Department2",
"JobTitle": "JobTitle2",
"FirstName": "FirstName2",
"LastName": "LastName2"
,
"Skill": "Painter",
"FirstName": "FirstName3",
"LastName": "LastName3"
]
我不想使用 TypeNameHandling JsonSerializerSettings。我正在专门寻找自定义 JsonConverter 实现来处理这个问题。这方面的文档和示例在网上非常稀少。我似乎无法在 JsonConverter 中正确实现重写的 ReadJson() 方法。
【问题讨论】:
另见how-to-deserialize-json-into-ienumerablebasetype-with-json-net 【参考方案1】:使用标准CustomCreationConverter
,我一直在努力研究如何生成正确的类型(Person
或Employee
),因为为了确定这一点,您需要分析 JSON 并且没有内置方法使用Create
方法来做到这一点。
我找到了一个关于类型转换的讨论帖,结果证明它提供了答案。这是一个链接:Type converting (archived link)。
所需的是将JsonConverter
子类化,覆盖ReadJson
方法并创建一个新的抽象Create
方法以接受JObject
。
JObject 类提供了一种加载 JSON 对象和 提供对该对象内数据的访问。
重写的ReadJson
方法创建一个JObject
并调用Create
方法(由我们的派生转换器类实现),传入JObject
实例。
然后可以分析这个JObject
实例,通过检查某些字段的存在来确定正确的类型。
示例
string json = "[
\"Department\": \"Department1\",
\"JobTitle\": \"JobTitle1\",
\"FirstName\": \"FirstName1\",
\"LastName\": \"LastName1\"
,
\"Department\": \"Department2\",
\"JobTitle\": \"JobTitle2\",
\"FirstName\": \"FirstName2\",
\"LastName\": \"LastName2\"
,
\"Skill\": \"Painter\",
\"FirstName\": \"FirstName3\",
\"LastName\": \"LastName3\"
]";
List<Person> persons =
JsonConvert.DeserializeObject<List<Person>>(json, new PersonConverter());
...
public class PersonConverter : JsonCreationConverter<Person>
protected override Person Create(Type objectType, JObject jObject)
if (FieldExists("Skill", jObject))
return new Artist();
else if (FieldExists("Department", jObject))
return new Employee();
else
return new Person();
private bool FieldExists(string fieldName, JObject jObject)
return jObject[fieldName] != null;
public abstract class JsonCreationConverter<T> : JsonConverter
/// <summary>
/// Create an instance of objectType, based properties in the JSON object
/// </summary>
/// <param name="objectType">type of object expected</param>
/// <param name="jObject">
/// contents of JSON object that will be deserialized
/// </param>
/// <returns></returns>
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
return typeof(T).IsAssignableFrom(objectType);
public override bool CanWrite
get return false;
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
【讨论】:
最好也实现WriteJson方法,并提供一些抽象方法来对类型进行字符串化。 注意:这个解决方案遍布互联网,但有一个在极少数情况下会表现出来的缺陷。在ReadJson
方法中创建的新JsonReader
不会继承任何原始阅读器的配置值(Culture
、DateParseHandling
、DateTimeZoneHandling
、FloatParseHandling
等...)。在serializer.Populate()
中使用新的JsonReader
之前,应复制这些值。
为了防止创建新的 JsonReader(由于@Alain 提到的原因),或者如果您需要根据某些父项的值来决定创建的对象类型,请参阅此解决方案 ***.com/a/22539730/1038496。对我来说似乎更有效和更清晰(即使是这种问题)。
@Triynko:找了很久才发现,JsonConverter
类有一个属性叫CanRead
和CanWrite
。如果你不需要自定义的WriteJson
实现,让CanWrite
返回FALSE
就足够了。然后系统将回退到默认行为。 @jdavies:请将其添加到您的答案中。否则会在序列化时崩溃。
我发现你必须处理NULL情况,否则会出现一个很好的错误。用途:||| if (reader.TokenType == JsonToken.Null) 返回 null; ||||来源:***.com/a/34185296/857291【参考方案2】:
JsonCreationConverter<T>
的上述解决方案遍布互联网,但在极少数情况下存在缺陷。在 ReadJson 方法中创建的新 JsonReader 不会继承任何原始阅读器的配置值(Culture、DateParseHandling、DateTimeZoneHandling、FloatParseHandling 等)。在 serializer.Populate() 中使用新的 JsonReader 之前,应复制这些值。
这是我能想出的最好的方法来解决上述实现的一些问题,但我仍然认为有些事情被忽略了:
更新我对此进行了更新,以使用一种更明确的方法来复制现有阅读器。这只是封装了复制单个 JsonReader 设置的过程。理想情况下,此函数将在 Newtonsoft 库本身中维护,但现在,您可以使用以下内容:
/// <summary>Creates a new reader for the specified jObject by copying the settings
/// from an existing reader.</summary>
/// <param name="reader">The reader whose settings should be copied.</param>
/// <param name="jToken">The jToken to create a new reader for.</param>
/// <returns>The new disposable reader.</returns>
public static JsonReader CopyReaderForObject(JsonReader reader, JToken jToken)
JsonReader jTokenReader = jToken.CreateReader();
jTokenReader.Culture = reader.Culture;
jTokenReader.DateFormatString = reader.DateFormatString;
jTokenReader.DateParseHandling = reader.DateParseHandling;
jTokenReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jTokenReader.FloatParseHandling = reader.FloatParseHandling;
jTokenReader.MaxDepth = reader.MaxDepth;
jTokenReader.SupportMultipleContent = reader.SupportMultipleContent;
return jTokenReader;
这应该按如下方式使用:
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
if (reader.TokenType == JsonToken.Null)
return null;
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
serializer.Populate(jObjectReader, target);
return target;
较旧的解决方案如下:
/// <summary>Base Generic JSON Converter that can help quickly define converters for specific types by automatically
/// generating the CanConvert, ReadJson, and WriteJson methods, requiring the implementer only to define a strongly typed Create method.</summary>
public abstract class JsonCreationConverter<T> : JsonConverter
/// <summary>Create an instance of objectType, based properties in the JSON object</summary>
/// <param name="objectType">type of object expected</param>
/// <param name="jObject">contents of JSON object that will be deserialized</param>
protected abstract T Create(Type objectType, JObject jObject);
/// <summary>Determines if this converted is designed to deserialization to objects of the specified type.</summary>
/// <param name="objectType">The target type for deserialization.</param>
/// <returns>True if the type is supported.</returns>
public override bool CanConvert(Type objectType)
// FrameWork 4.5
// return typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
// Otherwise
return typeof(T).IsAssignableFrom(objectType);
/// <summary>Parses the json to the specified type.</summary>
/// <param name="reader">Newtonsoft.Json.JsonReader</param>
/// <param name="objectType">Target type.</param>
/// <param name="existingValue">Ignored</param>
/// <param name="serializer">Newtonsoft.Json.JsonSerializer to use.</param>
/// <returns>Deserialized Object</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
if (reader.TokenType == JsonToken.Null)
return null;
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
//Create a new reader for this jObject, and set all properties to match the original reader.
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, target);
return target;
/// <summary>Serializes to the specified type</summary>
/// <param name="writer">Newtonsoft.Json.JsonWriter</param>
/// <param name="value">Object to serialize.</param>
/// <param name="serializer">Newtonsoft.Json.JsonSerializer to use.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
serializer.Serialize(writer, value);
【讨论】:
别忘了想想CanWrite! (我将其设置为 false)您可能会以 selfreferencingloops 结束(我做过)。 ***.com/questions/12314438/… 你不是也需要实现WriteJson吗?转换器如何知道如何从对象转换为 json?【参考方案3】:只是想我会基于此共享一个解决方案,该解决方案使用反射与 Knowntype 属性一起使用,必须从任何基类获取派生类,解决方案可以从递归中受益以找到最佳匹配类,尽管我不需要在我的情况下,匹配是由提供给转换器的类型完成的,如果它具有 KnownTypes,它将扫描它们,直到它匹配具有 json 字符串中所有属性的类型,将选择第一个匹配的类型。
用法很简单:
string json = " Name:\"Something\", LastName:\"Otherthing\" ";
var ret = JsonConvert.DeserializeObject<A>(json, new KnownTypeConverter());
在上述情况下,ret 将是 B 类型。
JSON 类:
[KnownType(typeof(B))]
public class A
public string Name get; set;
public class B : A
public string LastName get; set;
转换器代码:
/// <summary>
/// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer
/// Selected class will be the first class to match all properties in the json object.
/// </summary>
public class KnownTypeConverter : JsonConverter
public override bool CanConvert(Type objectType)
return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(objectType); // Reflection.
// Displaying output.
foreach (System.Attribute attr in attrs)
if (attr is KnownTypeAttribute)
KnownTypeAttribute k = (KnownTypeAttribute) attr;
var props = k.Type.GetProperties();
bool found = true;
foreach (var f in jObject)
if (!props.Any(z => z.Name == f.Key))
found = false;
break;
if (found)
var target = Activator.CreateInstance(k.Type);
serializer.Populate(jObject.CreateReader(),target);
return target;
throw new ObjectNotFoundException();
// Populate the object properties
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
throw new NotImplementedException();
【讨论】:
我真的很喜欢这个解决方案,但是当有多个已知类型具有相同的确切属性名称时,我会发现一个问题。你遇到过这个问题吗?谢谢。【参考方案4】:JsonSubTypes 项目实现了一个通用转换器,它借助属性处理此功能。
这里提供的具体示例是它的工作原理:
[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Employee), "JobTitle")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Artist), "Skill")]
public class Person
public string FirstName get; set;
public string LastName get; set;
public class Employee : Person
public string Department get; set;
public string JobTitle get; set;
public class Artist : Person
public string Skill get; set;
[TestMethod]
public void Demo()
string json = "[\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"," +
"\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"," +
"\"Skill\":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"]";
var persons = JsonConvert.DeserializeObject<IReadOnlyCollection<Person>>(json);
Assert.AreEqual("Painter", (persons.Last() as Artist)?.Skill);
【讨论】:
非常有用的转换器。刚刚节省了我自己编写转换器的时间!【参考方案5】:这是对图腾答案的扩展。它的作用基本相同,但属性匹配是基于序列化的 json 对象,而不是反映 .net 对象。如果您使用 [JsonProperty]、使用 CamelCasePropertyNamesContractResolver 或执行任何其他会导致 json 与 .net 对象不匹配的操作,这一点很重要。
用法很简单:
[KnownType(typeof(B))]
public class A
public string Name get; set;
public class B : A
public string LastName get; set;
转换器代码:
/// <summary>
/// Use KnownType Attribute to match a divierd class based on the class given to the serilaizer
/// Selected class will be the first class to match all properties in the json object.
/// </summary>
public class KnownTypeConverter : JsonConverter
public override bool CanConvert( Type objectType )
return System.Attribute.GetCustomAttributes( objectType ).Any( v => v is KnownTypeAttribute );
public override bool CanWrite
get return false;
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
// Load JObject from stream
JObject jObject = JObject.Load( reader );
// Create target object based on JObject
System.Attribute[ ] attrs = System.Attribute.GetCustomAttributes( objectType ); // Reflection.
// check known types for a match.
foreach( var attr in attrs.OfType<KnownTypeAttribute>( ) )
object target = Activator.CreateInstance( attr.Type );
JObject jTest;
using( var writer = new StringWriter( ) )
using( var jsonWriter = new JsonTextWriter( writer ) )
serializer.Serialize( jsonWriter, target );
string json = writer.ToString( );
jTest = JObject.Parse( json );
var jO = this.GetKeys( jObject ).Select( k => k.Key ).ToList( );
var jT = this.GetKeys( jTest ).Select( k => k.Key ).ToList( );
if( jO.Count == jT.Count && jO.Intersect( jT ).Count( ) == jO.Count )
serializer.Populate( jObject.CreateReader( ), target );
return target;
throw new SerializationException( string.Format( "Could not convert base class 0", objectType ) );
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
throw new NotImplementedException( );
private IEnumerable<KeyValuePair<string, JToken>> GetKeys( JObject obj )
var list = new List<KeyValuePair<string, JToken>>( );
foreach( var t in obj )
list.Add( t );
return list;
【讨论】:
【参考方案6】:作为 Totem 已知类型解决方案的另一种变体,您可以使用反射来创建泛型类型解析器,以避免使用已知类型属性。
这使用了类似于 WCF 的 Juval Lowy's GenericResolver 的技术。
只要您的基类是抽象类或接口,就会自动确定已知类型,而不必使用已知类型属性进行修饰。
在我自己的情况下,我选择使用 $type 属性来指定我的 json 对象中的类型,而不是尝试从属性中确定它,尽管您可以从此处借用其他解决方案来使用基于属性的确定。
public class JsonKnownTypeConverter : JsonConverter
public IEnumerable<Type> KnownTypes get; set;
public JsonKnownTypeConverter() : this(ReflectTypes())
public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
KnownTypes = knownTypes;
protected object Create(Type objectType, JObject jObject)
if (jObject["$type"] != null)
string typeName = jObject["$type"].ToString();
return Activator.CreateInstance(KnownTypes.First(x => typeName == x.Name));
else
return Activator.CreateInstance(objectType);
throw new InvalidOperationException("No supported type");
public override bool CanConvert(Type objectType)
if (KnownTypes == null)
return false;
return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
var target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
throw new NotImplementedException();
//Static helpers
static Assembly CallingAssembly = Assembly.GetCallingAssembly();
static Type[] ReflectTypes()
List<Type> types = new List<Type>();
var referencedAssemblies = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
foreach (var assemblyName in referencedAssemblies)
Assembly assembly = Assembly.Load(assemblyName);
Type[] typesInReferencedAssembly = GetTypes(assembly);
types.AddRange(typesInReferencedAssembly);
return types.ToArray();
static Type[] GetTypes(Assembly assembly, bool publicOnly = true)
Type[] allTypes = assembly.GetTypes();
List<Type> types = new List<Type>();
foreach (Type type in allTypes)
if (type.IsEnum == false &&
type.IsInterface == false &&
type.IsGenericTypeDefinition == false)
if (publicOnly == true && type.IsPublic == false)
if (type.IsNested == false)
continue;
if (type.IsNestedPrivate == true)
continue;
types.Add(type);
return types.ToArray();
然后可以将其安装为格式化程序
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new JsonKnownTypeConverter());
【讨论】:
【参考方案7】:很多时候,实现将存在于与接口相同的命名空间中。所以,我想出了这个:
public class InterfaceConverter : JsonConverter
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
var token = JToken.ReadFrom(reader);
var typeVariable = this.GetTypeVariable(token);
if (TypeExtensions.TryParse(typeVariable, out var implimentation))
else if (!typeof(IEnumerable).IsAssignableFrom(objectType))
implimentation = this.GetImplimentedType(objectType);
else
var genericArgumentTypes = objectType.GetGenericArguments();
var innerType = genericArgumentTypes.FirstOrDefault();
if (innerType == null)
implimentation = typeof(IEnumerable);
else
Type genericType = null;
if (token.HasAny())
var firstItem = token[0];
var genericTypeVariable = this.GetTypeVariable(firstItem);
TypeExtensions.TryParse(genericTypeVariable, out genericType);
genericType = genericType ?? this.GetImplimentedType(innerType);
implimentation = typeof(IEnumerable<>);
implimentation = implimentation.MakeGenericType(genericType);
return JsonConvert.DeserializeObject(token.ToString(), implimentation);
public override bool CanConvert(Type objectType)
return !typeof(IEnumerable).IsAssignableFrom(objectType) && objectType.IsInterface || typeof(IEnumerable).IsAssignableFrom(objectType) && objectType.GetGenericArguments().Any(t => t.IsInterface);
protected Type GetImplimentedType(Type interfaceType)
if (!interfaceType.IsInterface)
return interfaceType;
var implimentationQualifiedName = interfaceType.AssemblyQualifiedName?.Replace(interfaceType.Name, interfaceType.Name.Substring(1));
return implimentationQualifiedName == null ? interfaceType : Type.GetType(implimentationQualifiedName) ?? interfaceType;
protected string GetTypeVariable(JToken token)
if (!token.HasAny())
return null;
return token.Type != JTokenType.Object ? null : token.Value<string>("$type");
因此,您可以像这样在全局范围内包含它:
public static JsonSerializerSettings StandardSerializerSettings => new JsonSerializerSettings
Converters = new List<JsonConverter>
new InterfaceConverter()
;
【讨论】:
【参考方案8】:使用 totem 和 zlangner 的想法,我创建了一个KnownTypeConverter
,它将能够确定最合适的继承者,同时考虑到 json 数据可能没有可选元素。
因此,服务会发送一个 JSON 响应,其中包含一组文档(传入和传出)。文档既有一组共同的元素,也有不同的元素。 在这种情况下,与传出文档相关的元素是可选的,可能不存在。
在这方面,创建了一个基类Document
,其中包括一组通用属性。
还创建了两个继承类:
- OutgoingDocument
增加了两个可选元素"device_id"
和"msg_id"
;
- IncomingDocument
增加一个强制元素"sender_id"
;
任务是创建一个转换器,该转换器基于来自 KnownTypeAttribute 的 json 数据和信息,将能够确定最合适的类,使您可以保存最多接收到的信息。还应该考虑到json数据可能没有可选元素。 为了减少 json 元素和数据模型属性的比较次数,我决定不考虑基类的属性,只将继承类的属性与 json 元素相关联。
来自服务的数据:
"documents": [
"document_id": "76b7be75-f4dc-44cd-90d2-0d1959922852",
"date": "2019-12-10 11:32:49",
"processed_date": "2019-12-10 11:32:49",
"sender_id": "9dedee17-e43a-47f1-910e-3a88ff6bc258",
,
"document_id": "5044a9ac-0314-4e9a-9e0c-817531120753",
"date": "2019-12-10 11:32:44",
"processed_date": "2019-12-10 11:32:44",
],
"total": 2
数据模型:
/// <summary>
/// Service response model
/// </summary>
public class DocumentsRequestIdResponse
[JsonProperty("documents")]
public Document[] Documents get; set;
[JsonProperty("total")]
public int Total get; set;
// <summary>
/// Base document
/// </summary>
[JsonConverter(typeof(KnownTypeConverter))]
[KnownType(typeof(OutgoingDocument))]
[KnownType(typeof(IncomingDocument))]
public class Document
[JsonProperty("document_id")]
public Guid DocumentId get; set;
[JsonProperty("date")]
public DateTime Date get; set;
[JsonProperty("processed_date")]
public DateTime ProcessedDate get; set;
/// <summary>
/// Outgoing document
/// </summary>
public class OutgoingDocument : Document
// this property is optional and may not be present in the service's json response
[JsonProperty("device_id")]
public string DeviceId get; set;
// this property is optional and may not be present in the service's json response
[JsonProperty("msg_id")]
public string MsgId get; set;
/// <summary>
/// Incoming document
/// </summary>
public class IncomingDocument : Document
// this property is mandatory and is always populated by the service
[JsonProperty("sender_sys_id")]
public Guid SenderSysId get; set;
转换器:
public class KnownTypeConverter : JsonConverter
public override bool CanConvert(Type objectType)
return System.Attribute.GetCustomAttributes(objectType).Any(v => v is KnownTypeAttribute);
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
// load the object
JObject jObject = JObject.Load(reader);
// take custom attributes on the type
Attribute[] attrs = Attribute.GetCustomAttributes(objectType);
Type mostSuitableType = null;
int countOfMaxMatchingProperties = -1;
// take the names of elements from json data
HashSet<string> jObjectKeys = GetKeys(jObject);
// take the properties of the parent class (in our case, from the Document class, which is specified in DocumentsRequestIdResponse)
HashSet<string> objectTypeProps = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(p => p.Name)
.ToHashSet();
// trying to find the right "KnownType"
foreach (var attr in attrs.OfType<KnownTypeAttribute>())
Type knownType = attr.Type;
if(!objectType.IsAssignableFrom(knownType))
continue;
// select properties of the inheritor, except properties from the parent class and properties with "ignore" attributes (in our case JsonIgnoreAttribute and XmlIgnoreAttribute)
var notIgnoreProps = knownType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => !objectTypeProps.Contains(p.Name)
&& p.CustomAttributes.All(a => a.AttributeType != typeof(JsonIgnoreAttribute) && a.AttributeType != typeof(System.Xml.Serialization.XmlIgnoreAttribute)));
// get serializable property names
var jsonNameFields = notIgnoreProps.Select(prop =>
string jsonFieldName = null;
CustomAttributeData jsonPropertyAttribute = prop.CustomAttributes.FirstOrDefault(a => a.AttributeType == typeof(JsonPropertyAttribute));
if (jsonPropertyAttribute != null)
// take the name of the json element from the attribute constructor
CustomAttributeTypedArgument argument = jsonPropertyAttribute.ConstructorArguments.FirstOrDefault();
if(argument != null && argument.ArgumentType == typeof(string) && !string.IsNullOrEmpty((string)argument.Value))
jsonFieldName = (string)argument.Value;
// otherwise, take the name of the property
if (string.IsNullOrEmpty(jsonFieldName))
jsonFieldName = prop.Name;
return jsonFieldName;
);
HashSet<string> jKnownTypeKeys = new HashSet<string>(jsonNameFields);
// by intersecting the sets of names we determine the most suitable inheritor
int count = jObjectKeys.Intersect(jKnownTypeKeys).Count();
if (count == jKnownTypeKeys.Count)
mostSuitableType = knownType;
break;
if (count > countOfMaxMatchingProperties)
countOfMaxMatchingProperties = count;
mostSuitableType = knownType;
if (mostSuitableType != null)
object target = Activator.CreateInstance(mostSuitableType);
using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject))
serializer.Populate(jObjectReader, target);
return target;
throw new SerializationException($"Could not serialize to KnownTypes and assign to base class objectType reference");
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
throw new NotImplementedException();
private HashSet<string> GetKeys(JObject obj)
return new HashSet<string>(((IEnumerable<KeyValuePair<string, JToken>>) obj).Select(k => k.Key));
public static JsonReader CopyReaderForObject(JsonReader reader, JObject jObject)
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateFormatString = reader.DateFormatString;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
jObjectReader.MaxDepth = reader.MaxDepth;
jObjectReader.SupportMultipleContent = reader.SupportMultipleContent;
return jObjectReader;
PS: 在我的情况下,如果转换器没有选择任何继承者(如果 JSON 数据仅包含来自基类的信息或 JSON 数据不包含包含来自OutgoingDocument
的可选元素),则将创建OutgoingDocument
类的对象,因为它在KnownTypeAttribute
属性列表中列在首位。根据您的要求,您可以在这种情况下更改 KnownTypeConverter
的实现。
【讨论】:
【参考方案9】:这是另一个避免使用jObject.CreateReader()
的解决方案,而是创建一个新的JsonTextReader
(这是默认JsonCreate.Deserialze
方法使用的行为:
public abstract class JsonCreationConverter<T> : JsonConverter
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
return typeof(T).IsAssignableFrom(objectType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
if (reader.TokenType == JsonToken.Null)
return null;
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
StringWriter writer = new StringWriter();
serializer.Serialize(writer, jObject);
using (JsonTextReader newReader = new JsonTextReader(new StringReader(writer.ToString())))
newReader.Culture = reader.Culture;
newReader.DateParseHandling = reader.DateParseHandling;
newReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
newReader.FloatParseHandling = reader.FloatParseHandling;
serializer.Populate(newReader, target);
return target;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
serializer.Serialize(writer, value);
【讨论】:
以上是关于如何在 JSON.NET 中实现自定义 JsonConverter?的主要内容,如果未能解决你的问题,请参考以下文章