Десериализирайте неизменни класове с интерфейсен параметър в конструктора с помощта на 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 информация, за да се десериализира правилно. Справка: BulletProof Deserialization

    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; }, в противен случай всички свойства ще бъдат зададени на нула
  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