JSON에서 커스텀 Json Converter를 구현하는 방법.인터넷?
여기 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에 이어 목록으로 돌아가려면 어떻게 해야 하나요?< Person >
[
{
"Department": "Department1",
"JobTitle": "JobTitle1",
"FirstName": "FirstName1",
"LastName": "LastName1"
},
{
"Department": "Department2",
"JobTitle": "JobTitle2",
"FirstName": "FirstName2",
"LastName": "LastName2"
},
{
"Skill": "Painter",
"FirstName": "FirstName3",
"LastName": "LastName3"
}
]
TypeNameHandling JsonSerializerSettings를 사용하지 않습니다.이 문제를 해결하기 위해 특별히 커스텀 Json Converter 구현을 찾고 있습니다.이것에 관한 문서나 예는, 넷상에서는 매우 희박합니다.JsonConverter에서 덮어쓰기된 ReadJson() 메서드 구현을 제대로 할 수 없는 것 같습니다.
CustomCreationConverter
타입을 Person
★★★★★★★★★★★★★★★★★」Employee
하려면 , 를 JSON 을 사용하는 은, 「」, 「JSON」, 「JSON」, 「JSON」을 해 작성되어 있지 않기 때문입니다.Create
★★★★★★ 。
나는 유형 변환에 관한 토론 스레드를 찾았고, 그것이 답을 제공한다는 것을 알았다.다음은 링크입니다.Type converting (아카이브 링크)
것은 입니다.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;
}
}
의 「」의 .JsonCreationConverter<T>
인터넷 전체에 퍼져 있지만 드문 경우에서 나타나는 결함이 있습니다.ReadJson의 JsonReader(컬처, DateParseHandling, DateTimeZoneHandling, FloatParseHandling)입니다.이러한 값은 시리얼라이저에서 새 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);
}
}
reflection을 사용하여 Knowntype 속성으로 동작하는 솔루션도 공유한다고 생각했습니다.기본 클래스에서 파생된 클래스를 찾아야 합니다.솔루션은 최적의 일치 클래스를 찾기 위한 재귀의 이점을 얻을 수 있습니다.내 경우는 필요하지 않습니다.알고 있는 경우 컨버터에 지정된 유형에 따라 매칭이 이루어집니다.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();
}
}
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);
}
이것은 토템의 대답에 대한 확장이다.기본적으로 동일한 작업을 수행하지만 속성 조회는 .net 개체를 반영하지 않고 직렬화된 json 개체를 기반으로 합니다.이것은 [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;
}
}
Totem의 기존 유형 솔루션에 대한 또 다른 변형으로 리플렉션 기능을 사용하여 일반 유형 해결 프로그램을 생성하여 알려진 유형 속성을 사용할 필요가 없습니다.
이것은 Juval Lowy의 Generic Resolver for WCF와 유사한 기술을 사용합니다.
기본 클래스가 추상 또는 인터페이스인 경우 알려진 유형은 알려진 유형 속성으로 장식할 필요 없이 자동으로 결정됩니다.
저 자신의 경우 속성에서 결정하는 것이 아니라 $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());
대부분의 경우 구현은 인터페이스와 동일한 네임스페이스에 존재합니다.그래서 제가 생각해낸 건
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()
}
};
토템과 즐랑너의 아이디어를 사용하여, 나는 하나의 아이디어를 만들었다.KnownTypeConverter
json 데이터에는 옵션 요소가 없을 수 있다는 점을 고려하면서 가장 적절한 상속자를 결정할 수 있습니다.
따라서 서비스는 문서 배열(수신 및 발신)을 포함하는 JSON 응답을 보냅니다.문서에는 공통 요소 집합과 다른 요소 집합이 있습니다.이 경우 발신 문서와 관련된 요소는 선택 사항이며 없을 수 있습니다.
이 점에 있어서 베이스 클래스Document
는 공통 속성 세트를 포함하는 생성되었습니다.두 개의 상속자 클래스도 생성됩니다. -OutgoingDocument
2개의 옵션 요소 추가"device_id"
그리고."msg_id"
; -IncomingDocument
1개의 필수 요소 추가"sender_id"
;
작업은 알려진 json 데이터와 정보를 기반으로 변환기를 생성하는 것이었습니다.TypeAttribute는 수신한 정보의 최대량을 저장할 수 있는 가장 적절한 클래스를 판별할 수 있습니다.또한 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
이 상황에서.
여기 또 다른 솔루션이 있습니다.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);
}
}
언급URL : https://stackoverflow.com/questions/8030538/how-to-implement-custom-jsonconverter-in-json-net
'programing' 카테고리의 다른 글
많은 클래스가 없는 GSON 구문 분석 (0) | 2023.03.18 |
---|---|
readyState vs status==200 (0) | 2023.03.13 |
왜 앵글은JS 문서는 모델 지시문에 점을 사용합니까? (0) | 2023.03.13 |
Angular에서 링크 기능을 실행하기 전에 컨트롤러에서 데이터를 기다립니다.JS 지시어 (0) | 2023.03.13 |
ng-click과 ng-click의 차이점 (0) | 2023.03.13 |