Десериализовать неизменяемые классы с параметром интерфейса в конструкторе с помощью json.net

Я хочу создать несколько неизменяемых классов с помощью Json.net, но столкнулся с сообщением об ошибке: Не удалось создать экземпляр типа SolutionName.InterfaceClassName.Type является интерфейсом или абстрактным классом и не может быть создан.

Проблема:

У меня есть неизменяемый класс с некоторым реализованным интерфейсом.

public interface ISubClass1
    {
        int number { get; }
        string string1 { get; }
    }

Подкласс:

public class SubClass1 : ISubClass1
    {
        public int number { get; }
        public string string1 { get; }  

        public SubClass1(int a, string str1)
        {
            number = a;
            string1 = str1;
        }
    }

public class SubClass2 : ISubClass1
        {
            public int number { get; }
            public string string1 { get; }  
            public string string2 { get; }  

            public SubClass2(int a, string str1, string str2)
            {
                number = a;
                string1 = str1;
                string2 = str2;
            }
        }

Реализация:

class Class1
    {
        public int SomeInt{ get; }
        public ISubClass1 SomeInterface { get; }

        public Class1(int a, ISubClass1 subclass)
        {
            SomeInt= a;
            SomeInterface = subclass;
        }
    }

Я могу сериализовать этот объект, все работает. Но при десериализации будет ошибка

"Could not create an instance of type SolutionName.InterfaceClassName.Type is an interface or abstract class and cannot be instantiated"

Это связано с тем, что ISubClass1 subclass не может быть распознан Json.net.


person Middle    schedule 01.07.2020    source источник
comment
Здесь нет вопросов.   -  person InBetween    schedule 01.07.2020
comment
@InBetween Я мог бы сделать это в формате вопросов и ответов, пожалуйста, прочитайте первый абзац, и вы так относитесь к людям, которые делятся знаниями?   -  person Middle    schedule 01.07.2020
comment
Если знаешь ответ, зачем задавать вопрос?   -  person InBetween    schedule 01.07.2020
comment
@InBetween Потому что я искал решение 1 неделю, и здесь его не было. Итак, я хотел задокументировать этот процесс, чтобы помочь другим после того, как решу эту проблему?   -  person Middle    schedule 01.07.2020
comment
Да, ваше намерение очень похвально, но этот сайт так не работает. Если в SO нет совпадений, это либо из-за плохого поиска, либо из-за того, что это не распространенная проблема, либо люди обычно уже знают, как ее решить.   -  person InBetween    schedule 01.07.2020
comment
@Middle не публикуйте ответ в вопросе. Ответь на свой вопрос! И отметьте это как правильный ответ. Это что-то действительное.   -  person DrkDeveloper    schedule 01.07.2020
comment
@DrkDeveloper Я определенно могу это сделать.   -  person Middle    schedule 01.07.2020
comment
@InBetween Это предположение очень ошибочно. Я являюсь примером того, кто ищет ответ и не может его получить, и мне не пришлось тратить неделю времени, если такой пост присутствует.   -  person Middle    schedule 01.07.2020
comment
@dbc Связано да, пожалуйста, обратитесь к шагу 3 решения для ответов, отсутствующих во всех приведенных выше сообщениях.   -  person Middle    schedule 02.07.2020


Ответы (1)


Решение:

Вам понадобится собственный преобразователь Json, чтобы вы могли разрешать ISubclass1 с конкретными типами, которые вы хотите.

1. Добавьте этот универсальный преобразователь в свое решение.

    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);
    
        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;
        }
    }

2 - Затем наследуйте любой интерфейс, который вы хотите перевести, как в примере ниже. Обратите внимание, что проверка FieldExists проверяет поле, которое идентифицирует тип подкласса. Таким образом, Json не нужна информация $type для правильной десериализации. Ссылка: Пуленепробиваемая десериализация

    public class SubClass1Converter : JsonCreationConverter<ISubClass1>
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new InvalidOperationException("Use default serialization.");
        }
    
        protected override ISubClass1 Create(Type objectType, JObject jObject)
        {
            if (FieldExists("string2", jObject))
            {
                return new SubClass2();
            }
            else
            {
                return new SubClass1();
            }
        }
    
        private bool FieldExists(string fieldName, JObject jObject)
        {
            return jObject[fieldName] != null;
        }
    }

3. Вы изменили свои подклассы и реализации с помощью атрибутов Json.net, иначе при десериализации значения по умолчанию будут нулевыми. У вас есть неизменяемый класс с некоторым реализованным интерфейсом.

Некоторые ключевые моменты:

  1. [JsonProperty] должен присутствовать в каждом свойстве, иначе свойства будут считаться нулевыми;
  2. [public SubClass1() { }] должен присутствовать пустой конструктор для конвертеров, объявляющих пустой класс.
  3. `[JsonConstructor] ' должен быть помечен в конструкторе, который позволяет свойствам получать значения, передаваемые им в неизменяемых объектах.
  4. { get; } необходимо изменить на { get; private set; }, иначе все свойства будут установлены на null
  5. public ISubClass1 SomeInterface { get; private set;} в реализации необходимо помечать атрибутом [JsonConverter(typeof(SubClass1Converter))].

Подкласс:

    public class SubClass1 : ISubClass1
        {
            [JsonProperty] 
            public int number { get; private set;}
            [JsonProperty]
            public string string1 { get; private set;} 

            public SubClass1() { }

            [JsonConstructor]
            public SubClass1(int a, string str1)
            {
                number = a;
                string1 = str1;
            }
        }

    public class SubClass2 : ISubClass1
            {
                [JsonProperty] 
                public int number { get; private set;}
                [JsonProperty]
                public string string1 { get; private set;} 
                [JsonProperty]
                public string string2 { get; private set;}  

                public SubClass2() { }

                [JsonConstructor]
                public SubClass2(int a, string str1, string str2)
                {
                    number = a;
                    string1 = str1;
                    string2 = str2;
                }
            }

Реализация:

class Class1
    {
        [JsonProperty] 
        public int SomeInt{ get; private set;}
        [JsonProperty]
        [JsonConverter(typeof(SubClass1Converter))]
        public ISubClass1 SomeInterface { get; private set;}

        [JsonConstructor] 
        public Class1(int a, ISubClass1 subclass)
        {
            SomeInt= a;
            SomeInterface = subclass;
        }
    }

Применение:

JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
jsonSettings.Formatting = Formatting.Indented;
jsonSettings.Converters.Add(new SubClass1Converter()); //Optional
jsonSettings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; //Optional

string jsonStr = JsonConvert.SerializeObject(cls1, jsonSettings);

Class1 deserializedObject = JsonConvert.DeserializeObject<Class1>(jsonStr , jsonSettings);

Использованная литература:

person Middle    schedule 01.07.2020
comment
Отметь ответ как правильный, не забудь. - person DrkDeveloper; 01.07.2020