No se puede deserializar JSON como tipo abstracto

Aug 19 2020

Cuando realizo una prueba que está deserializando una cadena JSON, aparece el mensaje de error:

No se puede deserializar JSON como tipo abstracto: TestController.Item

El JSON contiene una lista de objetos que es abstracta y no es posible deserializarla.

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

Esta es la clase de prueba:

@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) {

        }
    }       
}

Encontré una solicitud de extracción de github con el mismo error. Hay una actualización crítica y estoy seguro de que esto causa el problema.

¿Tienes alguna idea de cómo solucionar ese problema?

Respuestas

5 sfdcfox Aug 19 2020 at 22:44

Incluso cuando este código se compilaba y ejecutaba, tenía un problema; en realidad, no podría crear una instancia de SomeItem dentro de la lista de Item, porque el analizador JSON no sabría qué subtipo realmente quiere usar.

Como tal, la Actualización Crítica es realmente algo bueno, porque corrige el comportamiento aberrante con este tipo de código. La solución es crear manualmente los objetos en la memoria.

Esto también apunta a una posible falla de diseño, si está destinado a usarse con algún tipo de API que usaría JSON. Simplemente no funcionará, porque el sistema no puede determinar el tipo correcto de subclase para usar para el tipo de datos, y en un lenguaje fuertemente tipado como Apex, eso es problemático.

Dado que todo está marcado como TestVisible, la solución correcta sería crear manualmente los objetos:

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

En resumen, no existe una solución alternativa directa. Debe poder deserializar en una clase concreta. Si no puede, entonces sus opciones son algo así como construir manualmente (arriba), usar JSONGenerator o usar un Objeto genérico (por ejemplo List<Map<String, Object>>).

2 RobAlexander Aug 22 2020 at 04:40

Me encontré con este problema hoy debido a la activación de la actualización crítica. Pude resolverlo cambiando mi clase abstracta a una clase virtual.