Невозможно десериализовать JSON как абстрактный тип

Aug 19 2020

При запуске теста десериализации строки JSON я получаю сообщение об ошибке:

Невозможно десериализовать JSON как абстрактный тип: TestController.Item

JSON содержит список объектов, который является абстрактным и десериализовать его невозможно.

public class TestController {
    
    @Testvisible
    private abstract class Item{
        public String bla;
        public String bla2;
    }


    public class SomeItem extends Item{
        private SomeItem() {
            bla = 'bla';
        }
    }


    public class SomeClass{

        @Testvisible private List<Item> items;

        public Graph(List<SomeItem> someItems) {
            items = new List<Item>();
            items.addAll((List<Item>) someItems);
        }
    }
}

Это тестовый класс:

@IsTest
public class TestController_Test {
    
    @IsTest
    private static void testSomething() {
        TestController.SomeClass someClass = (TestController.SomeClass) System.JSON.deserialize(json, TestController.SomeClass.class);

        for(TestController.Item item : someClass.items) {

        }
    }       
}

Я нашел запрос на перенос на github с той же ошибкой. Есть критическое обновление, и я уверен, что это вызывает проблему.

Вы знаете, как решить эту проблему?

Ответы

5 sfdcfox Aug 19 2020 at 22:44

Даже когда этот код компилировался и запускался, у него была проблема; вы не можете создать экземпляр SomeItem внутри списка Item, потому что парсер JSON не будет знать, какой подтип вы на самом деле хотите использовать.

По сути, критическое обновление - это действительно хорошо, потому что оно исправляет аномальное поведение с этим типом кода. Решение состоит в том, чтобы вручную создавать объекты в памяти.

Это также указывает на возможный недостаток дизайна, если он предназначен для использования с каким-либо API, который будет использовать JSON. Это просто не сработает, потому что система не может определить правильный тип подкласса для использования для данного типа данных, а на строго типизированном языке, таком как Apex, это проблематично.

Поскольку все помечено как TestVisible, правильным решением было бы вручную создать объекты:

TestController.SomeClass wrapper = new TestController.SomeClass();
wrapper.items = new TestController.SomeItem[0];
wrapper.items.add(new TestController.SomeItem());
// etc //

Таким образом, нет прямого обходного пути. Вы должны иметь возможность десериализоваться в конкретный класс. Если вы не можете, тогда ваши варианты похожи на построение вручную (см. Выше), с использованием JSONGenerator или с использованием общего объекта (например List<Map<String, Object>>).

2 RobAlexander Aug 22 2020 at 04:40

Я столкнулся с этой проблемой сегодня из-за активации критического обновления. Я смог решить эту проблему, изменив свой абстрактный класс на виртуальный.