Saltar al contenido

¿Cómo empiezo a trabajar con JSON en Apex?

Solución:

Apex proporciona múltiples rutas para lograr la serialización JSON y la deserialización de estructuras de datos. Esta respuesta resume los casos de uso y las capacidades de sin tipificar deserialización, mecanografiado (des) serialización, implementaciones manuales usando JSONGenerator y JSONParsery herramientas disponibles para ayudar a respaldar estos usos. No está destinado a responder todas las preguntas sobre JSON, sino a proporcionar una introducción, descripción general y enlaces a otros recursos.

Resumen

Apex puede serializar y deserializar JSON en clases Apex fuertemente tipadas y también en colecciones genéricas como Map y List. En la mayoría de los casos, es preferible definir clases de Apex que representen estructuras de datos y utilicen serialización y deserialización con tipos con JSON.serialize()/JSON.deserialize(). Sin embargo, algunos casos de uso requieren la aplicación de deserialización sin tipo con JSON.deserializeUntyped().

los JSONGenerator y JSONParser las clases están disponibles para implementaciones manuales y deben usarse solo donde la (des) serialización automática no sea factible, como cuando las claves en JSON son palabras reservadas o identificadores no válidos en Apex, o cuando se requiere acceso de bajo nivel.

Las referencias de documentación clave son las JSON class en Apex Developer Guide y la sección JSON Support. Otra documentación relevante está vinculada desde esas páginas.

Tipos complejos en Apex y JSON

JSON ofrece mapas (u objetos) y listas como sus tipos complejos. Las listas JSON se asignan a Apex List objetos. Los objetos JSON se pueden asignar a cualquiera Clases de Apex, con asignación de claves a variables de instancia o Apex Map objetos. Las clases y colecciones de Apex se pueden entremezclar libremente para construir las estructuras de datos correctas para cualquier objetivo JSON en particular.

A lo largo de esta respuesta, usaremos el siguiente JSON como ejemplo:


    "errors": [ "Data failed validation rules" ],
    "message": "Please edit and retry",
    "details": 
        "record": "001000000000001",
        "record_type": "Account"
    

Este JSON incluye dos niveles de objetos anidados, así como una lista de valores primitivos.

Serialización mecanografiada con JSON.serialize() y JSON.deserialize()

Los métodos JSON.serialize() y JSON.deserialize() convertir entre JSON y valores Apex con tipo. Cuando usas JSON.deserialize(), debe especificar el tipo de valor que espera que rinda JSON y Apex intentará deserializar a ese tipo. JSON.serialize() acepta tanto colecciones como objetos de Apex, en cualquier combinación que sea convertible a JSON legal.

Estos métodos son particularmente útiles al convertir JSON hacia y desde clases de Apex, que es en la mayoría de circunstancias el patrón de implementación preferido. El ejemplo JSON anterior se puede representar con la siguiente clase de Apex:


public class Example 
    public List errors;
    public String message;
    
    public class ExampleDetail 
        Id record;
        String record_type;
    
    
    public ExampleDetail details;

Para analizar JSON en un Example instancia, ejecutar

Example ex = (Example)JSON.deserialize(jsonString, Example.class);

Alternativamente, para convertir un Example instancia en JSON, ejecutar

String jsonString = JSON.serialize(ex);

Tenga en cuenta que los objetos JSON anidados se modelan con una clase de Apex por nivel de estructura. No es necesario que estas clases sean clases internas, pero es un patrón de implementación común. Apex solo permite un nivel de anidación para clases internas, por lo que las estructuras JSON profundamente anidadas a menudo se convierten en clases de Apex con todos los niveles de estructura definidos en clases internas en el nivel superior.

JSON.serialize() y JSON.deserialize() se puede utilizar con colecciones y clases de Apex en combinación para representar estructuras de datos JSON complejas. Por ejemplo, JSON que almacenó Example instancias como valores para claves de nivel superior:


    "first":  /* Example instance */ ,
    "second":  /* Example instance */,
    /* ... and so on... */

se puede serializar y deserializar en un Map valor en Apex.

Cabe señalar que este enfoque no funcionará cuando el JSON que se va a deserializar no se pueda asignar directamente a los atributos de la clase de Apex (por ejemplo, porque los nombres de propiedad JSON son palabras reservadas de Apex o no son válidos como identificadores de Apex (por ejemplo, contienen guiones u otros caracteres no válidos) .

Para obtener más información sobre la serialización y deserialización escritas, revise la JSON documentación de clase. Hay opciones disponibles para:

  • Supresión de null valores
  • JSON generado con bastante impresión
  • Deserialización estricta, que falla en atributos inesperados

Deserialización sin tipo con JSON.deserializeUntyped()

En algunas situaciones, es más beneficioso deserializar JSON en colecciones de valores primitivos de Apex, en lugar de en clases de Apex fuertemente tipadas. Por ejemplo, este puede ser un enfoque valioso cuando la estructura del JSON puede cambiar de formas que no son compatibles con la deserialización con tipo, o que requerirían características que Apex no ofrece, como los tipos algebraicos o de unión.

Utilizando el JSON.deserializeUntyped() método produce un Object valor, porque Apex no sabe en el momento de la compilación qué tipo de valor producirá JSON. Es necesario cuando se utiliza este método para encasillar valores omnipresentes.

Tomemos, por ejemplo, este JSON, que viene en múltiples variantes etiquetadas por un "scope" valor:


    "scope": "Accounts",
    "data": 
        "payable": 100000,
        "receivable": 40000
    

o


    "scope": 
        "division": "Sales",
        "organization": "International"
    ,
    "data": 
        "closed": 400000
    


La entrada JSON que varía de esta manera no se puede manejar con clases Apex fuertemente tipadas porque su estructura no es uniforme. Los valores de las claves scope y data tienen diferentes tipos.

Este tipo de estructura JSON se puede deserializar usando JSON.deserializeUntyped(). Ese método devuelve un Object, un valor sin tipo cuyo tipo real en tiempo de ejecución reflejará la estructura del JSON. En este caso, ese tipo sería Map, porque el nivel superior de nuestro JSON es un objeto. Podríamos deserializar este JSON a través de

Map result = (Map)JSON.deserializeUntyped(jsonString);

La naturaleza sin tipo del valor que obtenemos a cambio cae en cascada a lo largo de la estructura, porque Apex no conoce el tipo en el momento de la compilación de alguna de los valores (que, como se vio anteriormente, pueden ser heterogéneos) en este objeto JSON.

Como resultado, para acceder a valores anidados, debemos escribir código defensivo que inspeccione los valores y encasillados en cada nivel. El ejemplo anterior arrojará un TypeException si el tipo resultante no es el esperado.

Para acceder a los datos del primer elemento en el JSON anterior, podríamos hacer algo como esto:

Object result = JSON.deserializeUntyped(jsonString);

if (result instanceof Map) 
    Map resultMap = (Map)result;
    if (resultMap.get('scope') == 'Accounts' &&
        resultMap.get('data') instanceof Map) 
        Map data = (Map)resultMap.get('data');
    
        if (data.get('payable') instanceof Integer) 
            Integer payable = (Integer)data.get('payable');
            
            AccountsService.handlePayables(payable);
         else 
            // handle error
        
     else 
        // handle error
    
 else 
    // handle error

Si bien existen otras formas de estructurar dicho código, incluida la captura JSONException y TypeException, la necesidad de estar a la defensiva es una constante. El código que no actúa a la defensiva mientras trabaja con valores sin escribir es vulnerable a los cambios JSON que producen excepciones y modos de falla que no se manifestarán en muchas prácticas de prueba. Las excepciones comunes incluyen NullPointerException, al acceder descuidadamente a valores anidados, y TypeException, al lanzar un valor al tipo incorrecto.

Implementación manual con JSONGenerator y JSONParser

los JSONGenerator y JSONParser Las clases permiten que su aplicación construya y analice JSON manualmente.

El uso de estas clases implica escribir código explícito para manejar cada elemento del JSON. Utilizando JSONGenerator y JSONParser Por lo general, produce un código mucho más complejo (y mucho más largo) que el uso de las herramientas de serialización y deserialización integradas. Sin embargo, puede ser necesario en algunas aplicaciones específicas. Por ejemplo, JSON que incluye palabras reservadas de Apex como claves se puede manejar con estas clases, pero no se puede deserializar en clases nativas porque las palabras reservadas (como type y class) no se pueden utilizar como identificadores.

Como guía general, utilice JSONGenerator y JSONParser solo cuando tenga una razón específica para hacerlo. De lo contrario, esfuércese por utilizar la serialización y deserialización nativas, o utilice herramientas externas para generar código de análisis (ver más abajo).

Generando código con JSON2Apex

JSON2Apex es una aplicación Heroku de código abierto. JSON2Apex le permite pegar en JSON y genera el código Apex correspondiente para analizar ese JSON. La herramienta tiene como valor predeterminado la creación de clases nativas para la serialización y deserialización. Detecta automáticamente muchas situaciones en las que se requiere un análisis explícito y genera JSONParser código para deserializar JSON en objetos Apex nativos.

JSON2Apex no resuelve todos los problemas relacionados con el uso de JSON, y el código generado puede requerir revisión y ajuste. Sin embargo, es un buen lugar para comenzar una implementación, especialmente para los usuarios que recién están comenzando con JSON en Apex.

Soluciones alternativas comunes

El atributo JSON es una palabra reservada o un identificador no válido

Por ejemplo, puede tener JSON entrante que se parece a:

"currency": "USD", "unitPrice" : 10.00, "_mode": "production"

que desea deserializar en un tipo de Apex personalizado:

public class MyStuff 
  String currency;
  Decimal unitPrice;
  String _mode;

Pero currency no se puede utilizar como nombre de variable porque es una palabra reservada, ni _mode porque no es un identificador de Apex legal.

Una solución alternativa fácil es cambiar el nombre de la variable y preprocesar el JSON antes de deserializar:

public class MyStuff 
  String currencyX;     // in JSON as currency
  Decimal unitPrice;


MyStuff myStuff = (MyStuff) JSON.deserialize(theJson.replace('"currency":','"currencyX":'),
                                             MyStuff.class);

Sin embargo, tenga en cuenta que esta estrategia puede fallar en cargas útiles grandes. JSON2Apex es capaz de generar código de deserialización manual que también maneja identificadores no válidos, y la deserialización sin tipo es otra opción.

Vaya, acabo de darme cuenta de que se suponía que tenía que editar la respuesta … lo siento.

¡Excelente y detallada publicación de David sobre esto!

Aquí hay una publicación corta (complementaria):

  • JSON es muy simple, así que comience por comprender que: lea primero esta página de Introducción a JSON, al menos un par de veces
  • En el 100% de mi código utilizo la clase JSON; He usado JSONGenerator y JSONParser 0% del tiempo. (Vea el último punto a continuación).
  • Si desea clases generadas, explore lo que produce JSON2Apex.
  • Para manejar JSON donde las claves no son identificadores de Apex legales, usando Apex Map funciona bien. Puede generar JSON creando esos mapas de Apex y luego llamando JSON.serialize y puedes analizarlos llamando JSON.deserializeUntyped.

La agradable sintaxis de inicialización de Apex también ayuda aquí, por ejemplo:

Map root = new Map
    'awkward key' => 'awkward with "quotes" value',
    'nested object key' => new Map
        'key1' => 'value1',
        'key2' => true,
        'key3' => 123.456,
        'key4' => null
    ,
    'nested array key' => new List>
        new Map
            'another key1' => 'value1',
            'another key2' => true
        ,
        new Map
            'another key1' => 'value2',
            'another key2' => false
        
    
;

String jsonString = JSON.serializePretty(root);
System.debug(jsonString);

produce:


  "nested array key" : [ 
    "another key2" : true,
    "another key1" : "value1"
  , 
    "another key2" : false,
    "another key1" : "value2"
   ],
  "nested object key" : 
    "key4" : null,
    "key3" : 123.456,
    "key2" : true,
    "key1" : "value1"
  ,
  "awkward key" : "awkward with "quotes" value"

Si bien el orden de claves resultante es molesto, es un artefacto de implementación; el orden de las claves no es significativo en JSON.

Sección de Reseñas y Valoraciones

Acuérdate de que tienes el privilegio interpretar .

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Respuestas a preguntas comunes sobre programacion y tecnología