다양한 웹 사이트에서 JSON 개체를 가져 와서 (정보 스크래핑을 생각) C # 개체로 변환하는 리더를 설정하려고합니다. 현재 deserialization 프로세스에 JSON.NET을 사용하고 있습니다. 내가 겪고있는 문제는 클래스에서 인터페이스 수준 속성을 처리하는 방법을 모른다는 것입니다. 그래서 자연의 무언가 :
public IThingy Thing
오류가 발생합니다.
IThingy 유형의 인스턴스를 만들 수 없습니다. 유형은 인터페이스 또는 추상 클래스이며 인스턴스화 할 수 없습니다.
내가 작업하는 코드가 민감하고 단위 테스트가 매우 중요하기 때문에 Thingy와는 반대로 IThingy를 갖는 것이 상대적으로 중요합니다. Thingy와 같은 완전한 객체로는 원자 테스트 스크립트에 대한 객체 조롱이 불가능합니다. 인터페이스 여야합니다.
나는 한동안 JSON.NET의 문서를 살펴 봤는데,이 사이트에서 찾을 수있는 질문은 모두 1 년 전의 것입니다. 도움이 필요하세요?
또한 중요한 경우 내 앱은 .NET 4.0으로 작성되었습니다.
답변
@SamualDavis는 관련 질문 에서 훌륭한 솔루션을 제공했으며 여기에 요약하겠습니다.
인터페이스 속성이있는 구체적인 클래스로 JSON 스트림을 역 직렬화해야하는 경우 구체적인 클래스를 클래스 생성자에 대한 매개 변수로 포함 할 수 있습니다! NewtonSoft deserializer는 속성을 deserialize하기 위해 이러한 구체적인 클래스를 사용해야한다는 것을 알아낼만큼 똑똑합니다.
다음은 그 예입니다.
public class Visit : IVisit
{
/// <summary>
/// This constructor is required for the JSON deserializer to be able
/// to identify concrete classes to use when deserializing the interface properties.
/// </summary>
public Visit(MyLocation location, Guest guest)
{
Location = location;
Guest = guest;
}
public long VisitId { get; set; }
public ILocation Location { get; set; }
public DateTime VisitDate { get; set; }
public IGuest Guest { get; set; }
}
답변
( 이 질문 에서 복사 )
들어오는 JSON을 제어 할 수없는 경우 (따라서 $ type 속성이 포함되어 있는지 확인할 수없는 경우) 구체적인 유형을 명시 적으로 지정할 수있는 사용자 지정 변환기를 작성했습니다.
public class Model
{
[JsonConverter(typeof(ConcreteTypeConverter<Something>))]
public ISomething TheThing { get; set; }
}
이것은 구체적 유형을 명시 적으로 지정하는 동안 Json.Net의 기본 직렬 변환기 구현을 사용합니다.
이 블로그 게시물에서 개요를 볼 수 있습니다 . 소스 코드는 다음과 같습니다.
public class ConcreteTypeConverter<TConcrete> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
//assume we can convert to anything for now
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//explicitly specify the concrete type we want to create
return serializer.Deserialize<TConcrete>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
//use the default serialization - it works fine
serializer.Serialize(writer, value);
}
}
답변
변환기를 사용하는 이유는 무엇입니까? Newtonsoft.Json
이 정확한 문제를 해결하기 위한 기본 기능 이 있습니다.
설정 TypeNameHandling
에 JsonSerializerSettings
에TypeNameHandling.Auto
JsonConvert.SerializeObject(
toSerialize,
new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Auto
});
이것은 유형의 구체적인 인스턴스가 아니라 인터페이스 또는 추상 클래스로 유지되는 모든 유형을 json에 넣습니다.
직렬화 및 직렬화 해제에 동일한 설정을 사용하고 있는지 확인하십시오 .
나는 그것을 테스트했으며 목록에서도 매력처럼 작동합니다.
사이트 링크가있는 검색 결과 웹 결과
⚠️ 경고 :
알려진 신뢰할 수있는 소스의 json에만 이것을 사용하십시오. 사용자 snipsnipsnip 은 이것이 실제로 vunerability라고 올바르게 언급했습니다.
자세한 내용은 CA2328 및 SCS0028 을 참조하십시오.
소스 및 대체 수동 구현 : Code Inside 블로그
답변
여러 인터페이스 구현의 역 직렬화를 활성화하려면 JsonConverter를 사용할 수 있지만 속성을 통해서는 사용할 수 없습니다.
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Converters.Add(new DTOJsonConverter());
Interfaces.IEntity entity = serializer.Deserialize(jsonReader);
DTOJsonConverter는 구체적인 구현으로 각 인터페이스를 매핑합니다.
class DTOJsonConverter : Newtonsoft.Json.JsonConverter
{
private static readonly string ISCALAR_FULLNAME = typeof(Interfaces.IScalar).FullName;
private static readonly string IENTITY_FULLNAME = typeof(Interfaces.IEntity).FullName;
public override bool CanConvert(Type objectType)
{
if (objectType.FullName == ISCALAR_FULLNAME
|| objectType.FullName == IENTITY_FULLNAME)
{
return true;
}
return false;
}
public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
if (objectType.FullName == ISCALAR_FULLNAME)
return serializer.Deserialize(reader, typeof(DTO.ClientScalar));
else if (objectType.FullName == IENTITY_FULLNAME)
return serializer.Deserialize(reader, typeof(DTO.ClientEntity));
throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
DTOJsonConverter는 deserializer에만 필요합니다. 직렬화 프로세스는 변경되지 않습니다. Json 객체는 구체적인 유형 이름을 포함 할 필요가 없습니다.
이 SO 게시물 은 일반적인 JsonConverter를 사용하여 동일한 솔루션을 한 단계 더 제공합니다.
답변
추상 유형을 실제 유형에 매핑하려면이 클래스를 사용합니다.
public class AbstractConverter<TReal, TAbstract> : JsonConverter where TReal : TAbstract
{
public override Boolean CanConvert(Type objectType)
=> objectType == typeof(TAbstract);
public override Object ReadJson(JsonReader reader, Type type, Object value, JsonSerializer jser)
=> jser.Deserialize<TReal>(reader);
public override void WriteJson(JsonWriter writer, Object value, JsonSerializer jser)
=> jser.Serialize(writer, value);
}
… 역 직렬화 할 때 :
var settings = new JsonSerializerSettings
{
Converters = {
new AbstractConverter<Thing, IThingy>(),
new AbstractConverter<Thing2, IThingy2>()
},
};
JsonConvert.DeserializeObject(json, type, settings);
답변
Nicholas Westby는 멋진 기사 에서 훌륭한 솔루션을 제공했습니다 .
다음과 같은 인터페이스를 구현하는 가능한 많은 클래스 중 하나로 JSON을 역 직렬화하려는 경우 :
public class Person
{
public IProfession Profession { get; set; }
}
public interface IProfession
{
string JobTitle { get; }
}
public class Programming : IProfession
{
public string JobTitle => "Software Developer";
public string FavoriteLanguage { get; set; }
}
public class Writing : IProfession
{
public string JobTitle => "Copywriter";
public string FavoriteWord { get; set; }
}
public class Samples
{
public static Person GetProgrammer()
{
return new Person()
{
Profession = new Programming()
{
FavoriteLanguage = "C#"
}
};
}
}
사용자 지정 JSON 변환기를 사용할 수 있습니다.
public class ProfessionConverter : JsonConverter
{
public override bool CanWrite => false;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return objectType == typeof(IProfession);
}
public override void WriteJson(JsonWriter writer,
object value, JsonSerializer serializer)
{
throw new InvalidOperationException("Use default serialization.");
}
public override object ReadJson(JsonReader reader,
Type objectType, object existingValue,
JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var profession = default(IProfession);
switch (jsonObject["JobTitle"].Value())
{
case "Software Developer":
profession = new Programming();
break;
case "Copywriter":
profession = new Writing();
break;
}
serializer.Populate(jsonObject.CreateReader(), profession);
return profession;
}
}
그리고 “Profession”속성을 JsonConverter 속성으로 장식하여 사용자 지정 변환기를 사용하도록 알려야합니다.
public class Person
{
[JsonConverter(typeof(ProfessionConverter))]
public IProfession Profession { get; set; }
}
그런 다음 인터페이스를 사용하여 클래스를 캐스팅 할 수 있습니다.
Person person = JsonConvert.DeserializeObject<Person>(jsonString);
답변
시도해 볼 수있는 두 가지 :
try / parse 모델을 구현합니다.
public class Organisation {
public string Name { get; set; }
[JsonConverter(typeof(RichDudeConverter))]
public IPerson Owner { get; set; }
}
public interface IPerson {
string Name { get; set; }
}
public class Tycoon : IPerson {
public string Name { get; set; }
}
public class Magnate : IPerson {
public string Name { get; set; }
public string IndustryName { get; set; }
}
public class Heir: IPerson {
public string Name { get; set; }
public IPerson Benefactor { get; set; }
}
public class RichDudeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IPerson));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// pseudo-code
object richDude = serializer.Deserialize<Heir>(reader);
if (richDude == null)
{
richDude = serializer.Deserialize<Magnate>(reader);
}
if (richDude == null)
{
richDude = serializer.Deserialize<Tycoon>(reader);
}
return richDude;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Left as an exercise to the reader :)
throw new NotImplementedException();
}
}
또는 개체 모델에서 그렇게 할 수있는 경우 IPerson과 리프 개체간에 구체적인 기본 클래스를 구현하고 역 직렬화합니다.
첫 번째는 잠재적으로 런타임에 실패 할 수 있고 두 번째는 개체 모델을 변경해야하며 출력을 가장 낮은 공통 분모로 균질화합니다.