JSON Data Binding Wizard (Delphi 12)
Hace poco que ya está disponible la última versión de RAD Studio.
La versión 12 Athens trae bastantes novedades; Podéis ver la lista completa y explicada en la propia web de embarcadero:
En esta entrada me voy a centrar en el nuevo asistente «JSON Data Binding Wizard».
Anteriormente en otras entradas ya he realizado ejemplos para trabajar con ficheros JSON. A continuación os adjunto algunos links de entradas donde por diferentes necesidades he trabajado con archivos de este tipo:
- https://neftali.clubdelphi.com/nasa-la-foto-del-da/
- https://neftali.clubdelphi.com/hablando-del-tiempo-openweathermap-12/
- https://neftali.clubdelphi.com/obtener-informacin-de-una-cancin/
- https://neftali.clubdelphi.com/google-maps-api-codificacion-geografica-ii/
Es junto al XML el formato más utilizado para intercambio de datos en la web y mayoritariamennte usado cuando descargamos información desde servidores REST mediante API, como se hace en las entradas anteriores.
Lo habitual en versiones antiguas de Delphi, es utilizar una librería externa ya que Delphi no la trae integrada (lkJSON, SuperObject,…) y en las versiones nuevas de Delphi ya se puede utilizar la que trae el propio Delphi (System.Json, REST.Json).
Lo que he necesitado hacer en esos ejemplos, es leer la estructura de datos y navegar por esa estructura jerárquica del JSON (similar al XML) e ir saltando por diferentes nodos hasta encontrar la información que necesitamos. Si el archivo es muy grande y la estructura compleja con muchos niveles, esta navegación (y su implementación puede ser más o menos compleja). Para escribir debemos completar los diferentes nodos de la estructura para finalmente generar el JSON.
También en entradas anteriores he recomendado una utilidad «online» que a mi me ha resultado muy útil en bastantes ocasiones, sobre todo en aquellos casos donde las estructuras JSON son grandes y complejas. Se trata de Json To Delphi Class, que como su nombre indica, es una herramienta que a partir de un JSON, nos genera una unidad con una estructura de clases en Delphi equivalente al JSON y los métodos para leerlo/escribirlo. De forma que nosotros utilizamos directamente las clases para obtener los datos en lugar de navegar por el JSON.
Por ejemplo, para unos datos simples con esta estructura:
{ "nombre": "Andrés Díaz", "Trabajo": "Programador", "id": "199", "CreadoEn": "2020-02-20T11:00:28.107Z", "Contacto": { "Telefono":"+34666555444", "email":"a.diaz@abc.com" } }
Esta web nos generará unas clases como estas (y el resto de código necesario para utilizarlas):
type TContacto = class private FEmail: string; FTelefono: string; published property Email: string read FEmail write FEmail; property Telefono: string read FTelefono write FTelefono; end; TPersona = class(TJsonDTO) private FContacto: TContacto; [SuppressZero] FCreadoEn: TDateTime; FId: string; FNombre: string; FTrabajo: string; published property Contacto: TContacto read FContacto; property CreadoEn: TDateTime read FCreadoEn write FCreadoEn; property Id: string read FId write FId; property Nombre: string read FNombre write FNombre; property Trabajo: string read FTrabajo write FTrabajo; public constructor Create; override; destructor Destroy; override; end; |
Personalmente me parece muy útil no sólo por la facilidad que aporta y la claridad que añade al código, sino porque nos abstraemos si en algún momento los datos originales en JSON sufren cambios. Por ejemplo, en sucesivas versiones de una misma API.
JSON Data Binding Wizard
En la propia docWiki de embarcadero podemos encontrar algo de información y algunas pantallas del nuevo Wizard.
Con buen criterio Embarcadero ha decidido integrar dentro del IDE algo similar a esta utilidad utilizando las clases para JSON que ya estaban disponibles. De esta forma desde el menú de File/New/Other/Web podemos encontrar este Wizard.
Si hacemos un ejercicio similar al anterior (con la misma estructura JSON), pero en lugar de utilizar la herramienta online, utilizamos el Wizard que viene con la versión 12, podemos ver que el resultado es bastante similar al obtenido desde la web:
[JsonSerialize(jmAllPubProps)] TContacto = class() private FTelefono: Int64; Femail: string; public property Telefono: Int64 read FTelefono write FTelefono; property email: string read Femail write Femail; end; [JsonSerialize(jmAllPubProps)] TPersona = class() private Fnombre: string; FTrabajo: string; Fid: Integer; FCreadoEn: TDateTime; FContacto: TContacto; public constructor Create; destructor Destroy; override; class function FromJSON(const AValue: TJSONValue): TPersona; overload; static; class function FromJSON(const AValue: string): TPersona; overload; static; function ToJSONObject: TJSONValue; function ToJSONString: string; property nombre: string read Fnombre write Fnombre; property Trabajo: string read FTrabajo write FTrabajo; property id: Integer read Fid write Fid; property CreadoEn: TDateTime read FCreadoEn write FCreadoEn; property Contacto: TContacto read FContacto; end; |
Como veis la estructura se parece mucho a la anterior (como no podía ser de otra manera). Añade algunos atributos a las clases e incluye métodos en la clase principal para importar y exportar el JSON, pero por lo demás, en general son bastante similares.
A partir de esto, acceder a los datos es realmente simple. Por ejemplo, cargar esa estructura y acceder al email de contacto se puede hacer con estas 2 líneas de código:
// JSON_PERSONA en una constante que almacena el JSON anterior // Cargar el JSON en las clases TPersona y auxiliares var persona:TPersona := TJson.JsonToObject(JSON_PERSONA); // Aceder al mail de contaco ShowMessage('Email de contacto: ' + persona.Contacto.email); |
Habrá que añadir las units donde está la clase TPersona definida y REST.Json. En este caso el JSON se encuentra en la constante JSON_PERSONA.
Cuanto más complicado sea la estructura JSON, más a cuenta sale utilizar el Wizard y generar las clases.
Otro ejemplo, que podeís encontrar en esta web, es el que se usaba en esta entrada: «Hablando del tiempo… (OpenWeatherMap 1/2)» para acceder a los datos de tiempo atmosférico de una ubicación (ciudad, país) utilizando la API que brinda la web de OpenWeatherMap.
Por ejemplo, si realizamos una petición para obtener datos de Oymyakon (Rusia), por curiosidad, una de las ciudades habitadas más frías del mundo, obtenemos un JSON Similar a este:
{"message":"accurate","cod":"200","count":1,"list":[{"id":2122311,"name":"Oymyakon", "coord":{"lat":63.4667,"lon":142.8167},"main":{"temp":-24.79,"feels_like":-24.79,"temp_min":-24.79, "temp_max":-18,"pressure":1021,"humidity":95,"sea_level":1021,"grnd_level":931}, "dt":1701173390,"wind":{"speed":0.29,"deg":255},"sys":{"country":"RU"},"rain":null,"snow":null, "clouds":{"all":100},"weather":[{"id":804,"main":"Clouds","description":"nubes","icon":"04n"}]}]} |
Si generamos las clases con el Wizard, obtendremos una estructura de clases como esta:
type [JsonSerialize(jmAllPubProps)] TCoord = class(TPersistent) private Flat: Double; Flon: Double; public property lat: Double read Flat write Flat; property lon: Double read Flon write Flon; end; [JsonSerialize(jmAllPubProps)] TMain = class(TPersistent) private Ftemp: Double; Ffeels_like: Double; Ftemp_min: Double; Ftemp_max: Integer; Fpressure: Integer; Fhumidity: Integer; Fsea_level: Integer; Fgrnd_level: Integer; public property temp: Double read Ftemp write Ftemp; property feels_like: Double read Ffeels_like write Ffeels_like; property temp_min: Double read Ftemp_min write Ftemp_min; property temp_max: Integer read Ftemp_max write Ftemp_max; property pressure: Integer read Fpressure write Fpressure; property humidity: Integer read Fhumidity write Fhumidity; property sea_level: Integer read Fsea_level write Fsea_level; property grnd_level: Integer read Fgrnd_level write Fgrnd_level; end; [JsonSerialize(jmAllPubProps)] TWind = class(TPersistent) private Fspeed: Double; Fdeg: Integer; public property speed: Double read Fspeed write Fspeed; property deg: Integer read Fdeg write Fdeg; end; [JsonSerialize(jmAllPubProps)] TSys = class(TPersistent) private Fcountry: string; public property country: string read Fcountry write Fcountry; end; [JsonSerialize(jmAllPubProps)] TClouds = class(TPersistent) private Fall: Integer; public property all: Integer read Fall write Fall; end; [JsonSerialize(jmAllPubProps)] TWeather_1 = class(TPersistent) private Fid: Integer; Fmain: string; Fdescription: string; Ficon: string; public property id: Integer read Fid write Fid; property main: string read Fmain write Fmain; property description: string read Fdescription write Fdescription; property icon: string read Ficon write Ficon; end; [JsonSerialize(jmAllPubProps)] TList = class(TPersistent) private Fid: Integer; Fname: string; Fcoord: TCoord; Fmain: TMain; Fdt: Integer; Fwind: TWind; Fsys: TSys; Frain: string; Fsnow: string; Fclouds: TClouds; Fweather: TArray; public constructor Create; destructor Destroy; override; property id: Integer read Fid write Fid; property name: string read Fname write Fname; property coord: TCoord read Fcoord; property main: TMain read Fmain; property dt: Integer read Fdt write Fdt; property wind: TWind read Fwind; property sys: TSys read Fsys; property rain: string read Frain write Frain; property snow: string read Fsnow write Fsnow; property clouds: TClouds read Fclouds; property weather: TArray read Fweather write Fweather; end; [JsonSerialize(jmAllPubProps)] TWeather = class(TPersistent) private Fmessage: string; Fcod: Integer; Fcount: Integer; Flist: TArray; public destructor Destroy; override; class function FromJSON(const AValue: TJSONValue): TWeather; overload; static; class function FromJSON(const AValue: string): TWeather; overload; static; function ToJSONObject: TJSONValue; function ToJSONString: string; property message: string read Fmessage write Fmessage; property cod: Integer read Fcod write Fcod; property count: Integer read Fcount write Fcount; property list: TArray read Flist write Flist; end; |
A partir de aquí con un código sencillo y comprensible como este, podremos acceder a varios datos diseminados por el JSON.
// Cargar el JSON en la clase TWeather // NOTA: JSON_WEATHER es una constante que almacena el json anterior... {"message":"accurate","cod":"200",.... var weather:TWeather := TJson.JsonToObject(JSON_WEATHER); // acceder a varios datos Memo1.Lines.Add('Ciudad: ' + weather.list[0].name); Memo1.Lines.Add('País: ' + weather.list[0].sys.country); Memo1.Lines.Add(' Temperatura máx.: ' + weather.list[0].main.temp_max.ToString); Memo1.Lines.Add(' Temperatura mín.: ' + weather.list[0].main.temp_min.ToString); |
Obtendremos unos datos como los que se ven en la imagen:
Como veis la utilización una vez utilizado el Wizard es muy sencilla. Para mi, sobre todo, lo que aporta más que sencillez, es claridad en el código. No me gusta vez código donde se encuentran líneas y líneas que navegan por los diferentes nodos de estas estructuras (al igual que me pasa con los XML).
A partir de aquí sólo hay que practicar un poco porque la utilización es bastante simple. El Wizard nos permite modificar algunas cualidades del código que generamos.
La más interesante posíblemente sea que podemos escoger la librería que podemos utilizar para mapear/serializar el JSON.
- REST.Json
- System.JSON.Serializers
El código generado en ambos casos es muy similar y también el código que utilizaremos para leer/escribir los datos.
Si utilizamos el JSON anterior (TPersona) el código utilizando REST.Json sería este:
var persona:TPersona := TJson.JsonToObject(Memo1.Lines.Text); Memo2.Lines.Add('Nombre:' + persona.nombre); Memo2.Lines.Add(' Contacto telf.:' + persona.Contacto.Telefono.ToString); Memo2.Lines.Add(' Contacto email:' + persona.Contacto.email); |
Si utilizamos System.JSON.Serializers sería algo así:
TJSONMapper.SetDefaultLibrary('REST.Json'); var persona:TPersona := TJSONMapper.Default.FromObject(Memo1.Lines.Text); Memo2.Lines.Add('Nombre:' + persona.nombre); Memo2.Lines.Add(' Contacto telf.:' + persona.Contacto.Telefono.ToString); Memo2.Lines.Add(' Contacto email:' + persona.Contacto.email); |
Hasta aquí esta entrada. Sólo queda probar y experimentar con el Wizard.
Como siempre los comentarios y sugerencias son bienvenidas.
Hasta la próxima.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Hola, muy útil, aunque hay algo que no he entendido, de dónde sale JSON_WEATHER o JSON_PERSONA.
Saludos.
@casimiro
Hola [Casimiro].
Tal vez no ha quedado claro (ahora modificaré la entrada) para ampliar esa información. Esos 2 elementos son 2 constantes que almacenan el texto en JSON con los que queremos trabajar.
JSON_PERSONA es una constante que definimos con este valor:
E igual con la constante JSON_WEATHER con el Json anterior con datos referentes a tiempo climático
Buenos dias Neftali . Esta consulta es de un tema relacionado. Tu publicaste una respuesta en stackOverFlow (https://stackoverflow.com/questions/72561207/how-to-parse-json-in-delphi) que me ayudo muchisimo solo me queda una duda como obtengo la cantidad de items de las colecciones por ejemplo en ese post
Como ubico la cantidad de elementos de la coleccion root.content o como itero a traves de todos sus elementos que me daria la misma solucion. Y si tuviera un json anidado donde en el siguiente nivel tuviera una propiedad items la misma pregunta como itero a traves de todo sus elementos o como se el numero de ellos.
Agradecido de antemano por la respuesta
Hola Juan Carlos.
El Content al ser una lista tiene la propiedad Count.
Usando esa propiedad puedes saber cuantos elementos tiene esa lista.
Con un JSON como este (que tiene 2 elementos en el Content):
Puedes obtener cuantos elementos tiene el Content y acceder a ellos con un código como este: