Подтвердить что ты не робот

Как программно выбирать конструктор во время десериализации?

Я хотел бы десериализовать объект 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 в наши дни?

4b9b3361

Ответ 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;