Inicio > Código, Delphi, JSON, OOP > JSON Data Binding Wizard (Delphi 12)

JSON Data Binding Wizard (Delphi 12)

martes, 19 de diciembre de 2023 Dejar un comentario Ir a comentarios

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:

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.

5/5 - (1 voto)
Categories: Código, Delphi, JSON, OOP Tags: , ,
  1. casimiro
    miércoles, 20 de diciembre de 2023 a las 10:28 | #1

    Hola, muy útil, aunque hay algo que no he entendido, de dónde sale JSON_WEATHER o JSON_PERSONA.
    Saludos.

  2. jueves, 21 de diciembre de 2023 a las 16:03 | #2

    @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:

    const
      JSON_PERSONA = '{"nombre": "Andrés Díaz", 
    "Trabajo": "Programador", "id": "199", 
    "CreadoEn": "2020-02-20T11:00:28.107Z", 
    "Contacto": {"Telefono":"+34666555444",
     "email":"a.diaz@abc.com"}}'

    E igual con la constante JSON_WEATHER con el Json anterior con datos referentes a tiempo climático

(Antispam) Escribe los 4 números correspondientes a los números romanos del captcha (espacio vacío = 0). Por ejemplo, [ III V IV II ] se traduce en: 3542 Captcha cargando...