Как программно выбирать конструктор во время десериализации?
Я хотел бы десериализовать объект System.Security.Claims.Claim
, сериализованный следующим образом:
{
"Issuer" : "LOCAL AUTHORITY",
"OriginalIssuer" : "LOCAL AUTHORITY",
"Type" : "http://my.org/ws/2015/01/identity/claims/mytype",
"Value" : "myvalue",
"ValueType" : "http://www.w3.org/2001/XMLSchema#string"
}
То, что я получаю, это JsonSerializationException
:
Невозможно найти конструктор для типа System.Security.Claims.Claim. Класс должен либо иметь значение по умолчанию конструктор, один конструктор с аргументами или конструктор, отмеченный с атрибутом JsonConstructor.
После некоторого исследования я наконец понял смысл одного в приведенном выше сообщении: десериализатор JSON не может найти правильный конструктор, как есть - в случае типа Claim
- несколько конструкторов с аргументами (хотя существует конструктор с аргументами, соответствующими точно указанным выше свойствам).
Можно ли указать десериализатору, какой конструктор выбрать, не добавляя атрибут JsonConstructor
к типу mscorlib?
Даниэль Халан решил эту проблему с патчем для Json.NET несколько лет назад. Есть ли способ решить эту проблему без изменения Json.NET в наши дни?
Ответы
Ответ 1
Если невозможно добавить атрибут [JsonConstructor]
к целевому классу (потому что вы не являетесь владельцем кода), тогда обычным обходным путем является создание пользовательского JsonConverter
, как было предложено @James Thorpe в комментарии. Это довольно просто. Вы можете загрузить JSON в JObject
, а затем выбрать отдельные свойства для создания экземпляра Claim
. Вот код, который вам нужен:
class ClaimConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(System.Security.Claims.Claim));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
string type = (string)jo["Type"];
string value = (string)jo["Value"];
string valueType = (string)jo["ValueType"];
string issuer = (string)jo["Issuer"];
string originalIssuer = (string)jo["OriginalIssuer"];
return new Claim(type, value, valueType, issuer, originalIssuer);
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Чтобы использовать конвертер, просто передайте его экземпляр вызову метода JsonConvert.DeserializeObject<T>()
:
Claim claim = JsonConvert.DeserializeObject<Claim>(json, new ClaimConverter());
Fiddle: https://dotnetfiddle.net/7LjgGR
Ответ 2
Другой подход, который по крайней мере будет работать для непечатаемых классов, заключается в подклассе, но только с конструктором, который вас интересует:
class MyClaim : Claim {
public MyClaim(string type, string value, string valueType, string issuer, string originalIssuer):
base(type, value, valueType, issuer, originalIssuer){}
}
Затем вы можете выполнить десериализацию этого объекта без вспомогательных классов, а затем обработать его как базовый тип.
Claim claim = JsonConvert.DeserializeObject<MyClaim>(json);
Для закрытых классов вы можете использовать этот подход (делая вид, что Claim
запечатан):
class MyClaim {
private Claim _claim;
public MyClaim(string type, string value, string valueType, string issuer, string originalIssuer) {
_claim = new Claim(type, value, valueType, issuer, originalIssuer);
}
public Claim Value { get {
return _claim;
}
}
}
Claim claim = JsonConvert.DeserializeObject<MyClaim>(json).Value;