인터페이스를 반환하는 속성을 포함하는 클래스 정의가 있습니다.
public class Foo
{
public int Number { get; set; }
public ISomething Thing { get; set; }
}
Json.NET을 사용하여 Foo 클래스를 직렬화하려고하면 ” ‘ISomething’유형의 인스턴스를 만들 수 없습니다. ISomething은 인터페이스 또는 추상 클래스 일 수 있습니다.”와 같은 오류 메시지가 나타납니다.
Something
deserialization 중에 사용할 구체적인 클래스를 지정할 수있는 Json.NET 특성 또는 변환기가 있습니까?
답변
Json.NET으로 할 수있는 작업 중 하나는 다음과 같습니다 .
var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
JsonConvert.SerializeObject(entity, Formatting.Indented, settings);
이 TypeNameHandling
플래그는 $type
JSON에 속성을 추가하여 Json.NET이 개체를 역 직렬화해야하는 구체적인 유형을 알 수 있도록합니다. 이를 통해 인터페이스 또는 추상 기본 클래스를 수행하면서 객체를 역 직렬화 할 수 있습니다.
그러나 단점은 이것이 매우 Json.NET 전용이라는 것입니다. 는 $type
정규화 된 유형이므로 유형 정보로 직렬화하는 경우 deserializer도이를 이해할 수 있어야합니다.
문서 : Json.NET을 사용한 직렬화 설정
답변
JsonConverter 클래스를 사용하여이를 달성 할 수 있습니다. 인터페이스 속성이있는 클래스가 있다고 가정합니다.
public class Organisation {
public string Name { get; set; }
[JsonConverter(typeof(TycoonConverter))]
public IPerson Owner { get; set; }
}
public interface IPerson {
string Name { get; set; }
}
public class Tycoon : IPerson {
public string Name { get; set; }
}
JsonConverter는 기본 속성의 직렬화 및 역 직렬화를 담당합니다.
public class TycoonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPerson));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<Tycoon>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Left as an exercise to the reader :)
throw new NotImplementedException();
}
}
Json.Net을 통해 역 직렬화 된 조직에서 작업 할 때 Owner 속성의 기본 IPerson은 Tycoon 유형이됩니다.
답변
TypeNameHandling.Objects 옵션을 사용하여 사용자 지정된 JsonSerializerSettings 개체를 JsonConvert.SerializeObject ()에 전달하는 대신 앞서 언급 한 것처럼 특정 인터페이스 속성을 속성으로 표시하여 생성 된 JSON이 “$ type”속성으로 부풀어지지 않도록 할 수 있습니다. 모든 개체 :
public class Foo
{
public int Number { get; set; }
// Add "$type" property containing type info of concrete class.
[JsonProperty( TypeNameHandling = TypeNameHandling.Objects )]
public ISomething { get; set; }
}
답변
최신 버전의 타사 Newtonsoft Json 변환기에서 인터페이스 속성과 관련된 구체적인 유형으로 생성자를 설정할 수 있습니다.
public class Foo
{
public int Number { get; private set; }
public ISomething IsSomething { get; private set; }
public Foo(int number, Something concreteType)
{
Number = number;
IsSomething = concreteType;
}
}
Something이 ISomething을 구현하는 한 작동합니다. 또한 JSon 변환기가이를 사용하려는 경우 기본 빈 생성자를 넣지 마십시오. 구체적인 유형을 포함하는 생성자를 사용하도록 강제해야합니다.
추신. 이것은 또한 세터를 비공개로 만들 수 있습니다.
답변
같은 문제가 있었으므로 알려진 유형 인수를 사용하는 내 변환기를 생각해 냈습니다.
public class JsonKnownTypeConverter : JsonConverter
{
public IEnumerable<Type> KnownTypes { get; set; }
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.Contains("."+x.Name+",")));
}
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();
}
}
역 직렬화 및 직렬화를위한 두 가지 확장 메서드를 정의했습니다.
public static class AltiJsonSerializer
{
public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null)
{
if (string.IsNullOrEmpty(jsonString))
return default(T);
return JsonConvert.DeserializeObject<T>(jsonString,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
Converters = new List<JsonConverter>
(
new JsonConverter[]
{
new JsonKnownTypeConverter(knownTypes)
}
)
}
);
}
public static string SerializeJson(this object objectToSerialize)
{
return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented,
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
}
}
개종자의 유형을 비교하고 식별하는 자신의 방법을 정의 할 수 있습니다. 저는 클래스 이름 만 사용합니다.
답변
일반적으로 TypeNameHandling
DanielT에서 제안한대로 솔루션을 항상 사용 했지만 여기서는 들어오는 JSON을 제어하지 않았기 때문에 $type
속성 이 포함되어 있는지 확인할 수 없습니다. 명시 적으로 지정할 수있는 사용자 지정 변환기를 작성했습니다. 구체적인 유형 :
public class Model
{
[JsonConverter(typeof(ConcreteTypeConverter<Something>))]
public ISomething TheThing { get; set; }
}
이것은 구체적 유형을 명시 적으로 지정하는 동안 Json.Net의 기본 직렬 변환기 구현을 사용합니다.
소스 코드와 개요는 이 블로그 게시물 에서 볼 수 있습니다 .
답변
@Daniel T.가 위에서 보여준 예제를 완성하고 싶었습니다.
이 코드를 사용하여 객체를 직렬화하는 경우 :
var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
JsonConvert.SerializeObject(entity, Formatting.Indented, settings);
json을 deserialize하는 코드는 다음과 같습니다.
var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
var entity = JsonConvert.DeserializeObject<EntityType>(json, settings);