lunes, 16 de septiembre de 2019

Parseando JSON complejo en Flutter

Créditos: Pooja Bhaumik (https://medium.com/@poojabhaumik)


Tengo que admitirlo, Me estaba perdiendo el mundo gson de Android después de trabajar con JSON en Flutter/Dart. Cuando comencé a trabajar con APIs en Flutter, el uso de parsing en JSON me hizo luchar mucho. Y estoy segura, confunde a muchos de ustedes principiantes.
Estaremos usando la librería incorporada dart:convert para este blog. Este es el método de parsing más básico y sólo se recomienda si está comenzando con Flutter o si está construyendo un proyecto pequeño. Sin embargo, conocer los conceptos básicos de parsing de JSON en Flutter es bastante importante. Cuando seas bueno en esto, o si necesita trabajar con un proyecto más grande, considere bibliotecas generadoras de código como json_serializable, etc. Si es posible, las descubriré en los próximos artículos.
Haga un fork de este proyecto de ejemplo. Tiene todo el código para este blog con el que puedes experimentar.

Estructura JSON #1: Map simple

Vamos a empezar con una estructura jSON simple desde students.json
{
"id":"487349",
"name":"Pooja Bhaumik",
"score" : 1000
}
Regla #1 : Identifique la estructura. Las cadenas Json tendrán un Map (pares clave-valor) o una Lista de Maps.
Regla #2 : Comienza con llaves? Es un map. Comienza con corchetes? Es una Lista de maps.
student.json es claramente un map. (Por ejemplo, id es una llave, y 487349 es el valor de id)
Vamos a hacer un archivo PODO (Plain Old Dart Object?) para esta estructura json. Puede encontrar este código en student_model.dart en el proyecto de ejemplo.
class Student{
  String studentId;
  String studentName;
  int studentScores;

  Student({
    this.studentId,
    this.studentName,
    this.studentScores
 });}
Perfecto!.
¿Era que? Porque no hubo asignación entre los maps json y este archivo PODO. Incluso los nombres de las entidades no coinciden.
Lo sé, lo sé. No hemos terminado todavía. Tenemos que hacer el trabajo de asignar estos miembros de clase al objeto json. Para eso, necesitamos crear un método factory. De acuerdo con la documentación de Dart, usamos la palabra clave factory cuando implementamos un constructor que no siempre crea una nueva instancia de su clase y eso es lo que necesitamos ahora.
factory Student.fromJson(Mapdynamic> parsedJson){
    return Student(
      studentId: parsedJson['id'],
      studentName : parsedJson['name'],
      studentScores : parsedJson ['score']
    );
  }
Aquí, estamos creando un método factory llamado Student.fromJson cuyo objetivo es simplemente deserializar su json.
Soy un poco novato, ¿puedes hablarme sobre la deserialización?
Por supuesto. Vamos a hablarte primero sobre la serialización y deserialización. Serialización simplemente significa escribir los datos (que podrían estar en un objeto) como una cadena, y la Deserialización es lo opuesto a eso. Toma los datos en bruto y reconstruye el modelo de objetos. En este artículo, nos ocuparemos principalmente de la parte de deserialización. En esta primera parte, estamos deserializando la cadena json de student.json
Así que nuestro método factory podría ser llamado como nuestro método de conversión.
También debes notar el parámetro en el método fromJson. Es un map Significa que mapea una clave cadena con un valor dinámico. Es exactamente por eso que necesitamos identificar la estructura. Si esta estructura json fuera una lista de mapas, entonces este parámetro habría sido diferente.
Pero porqué dinamico?
Veamos primero otra estructura json para responder tu pregunta.

name is un Mapmajors es un Map de String y List y subjects es un map de String y List
Dado que la clave es siempre una cadena y el valor puede ser de cualquier tipo, lo mantenemos como dinámico para estar en el lado seguro.
Vea el código completo de student_model.dart aquí.

Accediendo al objeto

Escribamos student_services.dart que tendrá el código para llamar a Student.fromJson y recuperar los valores del objeto Student.

Snippet #1 : imports

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
import 'package:flutter_json/student_model.dart';
El último import será el nombre de su archivo model.

Snippet #2 : cargar el Asset Json (opcional)

Future _loadAStudentAsset() async {
  return await rootBundle.loadString('assets/student.json');
}
En este proyecto en particular, tenemos nuestros archivos json en la carpeta Assets, por lo que tenemos que cargar el json de esta manera. Pero si tiene su archivo json en la nube, puede hacer una llamada de red en su lugar. Las llamadas de red están fuera del alcance de este artículo.

Snippet #3 : cargar el response

Future loadStudent() async {
  String jsonString = await _loadAStudentAsset();
  final jsonResponse = json.decode(jsonString);
  Student student = new Student.fromJson(jsonResponse);
  print(student.studentScores);
}
In this método loadStudent(),
Line 1 : cargando el json string crudo desde los assets.
Line 2 : Decodificando esta cadena json cruda que tenemos.
Line 3 : Y ahora estamos deserializando la respuesta json decodificada llamando al método Student.fromJson para que podamos usar el objetoStudent para acceder a nuestras entidades.
Line 4 : Como hicimos aquí, donde nosotros imprimimos studentScores desde la clase Studentclass.
Revise su consola Flutter para ver todos sus valores de impresión. (En Android Studio, está bajo la pestaña Ejecutar)
¡Y voilá! Acabas de hacer tu primer parsing JSON (o no).
Nota: recuerde los 3 fragmentos de código aquí, lo usaremos para el siguiente conjunto de parsing json (solo cambiando los nombres de archivo y de método), y no repetiré el código nuevamente aquí. Pero puedes encontrar todo en el proyecto de muestra de todos modos.

Estructura JSON #2 : Estructura simple con arrays

Ahora conquistamos una estructura json que es similar a la anterior, pero en lugar de valores simples, también puede tener un array de valores.
{
  "city": "Mumbai",
  "streets": [
    "address1",
    "address2"
  ]
}
Así que en esta address.json, tenemos una entidadcity que tiene un valorString simple, perostreets es un array deString.
Por lo que sé, Dart no tiene un tipo de datos de matriz, pero en cambio tiene un List, por lo que aquístreets será unList.
Ahora tenemos que verificar la Regla # 1 y la Regla # 2. Este es definitivamente un Map ya que comienza con una llave. Sin embargo, streets sigue siendo List, pero eso nos preocupará más adelante.
Así que address_model.dart inicialmente se verá así
class Address {
  final String city;
  final List streets;

  Address({
    this.city,
    this.streets
  });
}
Ahora, debido a que este es un map, nuestro método Address.fromJson todavía tendrá un parámetro Map.
factory Address.fromJson(Mapdynamic> parsedJson) {
  
  return new Address(
      city: parsedJson['city'],
      streets: parsedJson['streets'],
  );
}
Ahora construya el archivo address_services.dart añadiendo los tres trozos de cigo que mencionamos arriba. Debe recordar poner los nombres de archivo y los nombres de método apropiados. El proyecto de ejemplo ya tiene el archivoaddress_services.dart construido para ti.
Ahora, cuando ejecutes esto, obtendrás un pequeño error. : /
type 'List' is not a subtype of type 'List'
Te digo, estos errores se han producido en casi todos los pasos de mi desarrollo con Dart. Y los tendrás también. Así que déjame explicarte lo que esto significa. Estamos requiriendo un List pero estamos obteniendo un List porque nuestra aplicación no puede identificar el tipo todavía.
Así que tenemos que convertir explícitamente esto a un List
var streetsFromJson = parsedJson['streets'];
List streetsList = new List.from(streetsFromJson);
Aquí, primero estamos mapeando nuestra variable streetsFromJson a la entidad streetsstreetsFromJson sigue siendo todavía List. Ahora creamos explícitamente un nuevo List streetsList que contiene todos los elementos de streetsFromJson.
Compruebe el método actualizado aquí. Observe la declaración de devolución ahora. Puede ejecutar esto con address_services.dart y esto funcionará perfectamente.

Estructura Json #3 : Estructuras anidadas simples

Ahora si tenemos una estructura anidada como esta de shape.json
{
  "shape_name":"rectangle",
  "property":{
    "width":5.0,
    "breadth":10.0
  }
}
Aquí, property contiene un objeto en vez de un tipo de dato primitivo básico. Entonces, ¿cómo se verá el PODO?
Está bien, vamos a romper un poco.
En nuestro shape_model.dart, primero hagamos una clase para Property.
class Property{
  double width;
  double breadth;

  Property({
    this.width,
    this.breadth
});
}
Ahora, vamos a construir la clase para Shape. Estoy manteniendo ambas clases en el mismo archivo Dart.
class Shape{
  String shapeName;
  Property property;

  Shape({
    this.shapeName,
    this.property
  });
}
Note cómo el segundo miembro de dato property es básicamente un objeto de nuestra clase Property vista anteriormente.
Regla #3: Para estructuras anidadas, haga las clases y constructores primero, y luego añada los métodos factory desde el nivel inferior.
Por nivel inferior, queremos decir, primero conquistamos la clase Property y luego subimos un nivel por encima de la clase Shape. Esta es solo mi sugerencia, no una regla de Flutter.
factory Property.fromJson(Mapdynamic> json){
  return Property(
    width: json['width'],
    breadth: json['breadth']
  );
}
Esto fue un simple map.
Pero para nuestro método factory en la clase Shape, podemos sólo hacer esto:
factory Shape.fromJson(Mapdynamic> parsedJson){
  return Shape(
    shapeName: parsedJson['shape_name'],
    property : parsedJson['property']
  );
}
property : parsedJson['property'] Primero, esto lanzará el error de tipo mismatch —
type '_InternalLinkedHashMap' is not a subtype of type 'Property'
Y segundo, hey, acabamos de hacer esta pequeña clase para Property, no veo su uso en ninguna parte.
Correcto. Debemos mapear nuestra clase Property aquí.
factory Shape.fromJson(Mapdynamic> parsedJson){
  return Shape(
    shapeName: parsedJson['shape_name'],
    property: Property.fromJson(parsedJson['property'])
  );
}
Básicamente, estamos llamando al método Property.fromJson desde nuestra clase Property y, a pesar de lo que obtengamos a cambio, lo asignamos a la entidad property. ¡Sencillo! Echa un vistazo al código aquí.
Ejecute esto con su shape_services.dart y estará listo.

Estructura JSON #4 : Estructuras anidadas con Listas

Revisemos nuestro product.json
{
  "id":1,
  "name":"ProductName",
  "images":[
    {
      "id":11,
      "imageName":"xCh-rhy"
    },
    {
      "id":31,
      "imageName":"fjs-eun"
    }
  ]
}
Está bien, ahora estamos profundizando. Vemos una lista de objetos en algún lugar dentro.
Si, esta estructura tiene una Lista de objetos, pero en si mismo es todavía un map. (Vea la Regla #1, y Regla #2). Ahora, refiriéndonos a la Regla #3, vamos a construir nuestroproduct_model.dart.
Entonces creamos dos nuevas clases Product yImage.
Nota: Product tendrá un miembro de dato que es una Lista deImage
class Product {
  final int id;
  final String name;
  final List images;

  Product({this.id, this.name, this.images});
}

class Image {
  final int imageId;
  final String imageName;

  Image({this.imageId, this.imageName});
}
El método factory paraImage será un poco simple y básico.
factory Image.fromJson(Mapdynamic> parsedJson){
 return Image(
   imageId:parsedJson['id'],
   imageName:parsedJson['imageName']
 );
}
Ahora para el método factory de Product
factory Product.fromJson(Mapdynamic> parsedJson){

  return Product(
    id: parsedJson['id'],
    name: parsedJson['name'],
    images: parsedJson['images']
  );
}
Esto obviamente lanzará un error de tiempo de ejecución
type 'List' is not a subtype of type 'List'
Y si hacemos esto,
images: Image.fromJson(parsedJson['images'])
Esto también está definitivamente incorrecto, y te lanzará un error inmediatamente porque usted no puede asignar un Objeto Image a una List
Entonces tenemos que crear un List y luego asignarlo a images
var list = parsedJson['images'] as List;
print(list.runtimeType); //returns ListList imagesList = list.map((i) => Image.fromJson(i)).toList();
list aquí es una List. Ahora, iteramos sobre la lista y mapear cada objeto en list aImage llamando Image.fromJson y luego ponemos cada objeto map en una nueva lista con toList() y almacenarlo en List imagesList. Encuentra el código completo aquí.

Estrutura JSON #5 : Lista de maps

Ahora vamos a photo.json
[
  {
    "albumId": 1,
    "id": 1,
    "title": "accusamus beatae ad facilis cum similique qui sunt",
    "url": "http://placehold.it/600/92c952",
    "thumbnailUrl": "http://placehold.it/150/92c952"
  },
  {
    "albumId": 1,
    "id": 2,
    "title": "reprehenderit est deserunt velit ipsam",
    "url": "http://placehold.it/600/771796",
    "thumbnailUrl": "http://placehold.it/150/771796"
  },
  {
    "albumId": 1,
    "id": 3,
    "title": "officia porro iure quia iusto qui ipsa ut modi",
    "url": "http://placehold.it/600/24f355",
    "thumbnailUrl": "http://placehold.it/150/24f355"
  }
]
La regla # 1 y la regla # 2 me dicen que esto no puede ser un map porque la cadena json comienza con un corchete. ¿Así que esta es una lista de objetos? Sí. El objeto que está aquí esPhoto (o como quieras llamarlo).
class Photo{
  final String id;
  final String title;
  final String url;

  Photo({
    this.id,
    this.url,
    this.title
}) ;

  factory Photo.fromJson(Mapdynamic> json){
    return new Photo(
      id: json['id'].toString(),
      title: json['title'],
      url: json['json'],
    );
  }
}
Pero esto es una lista dePhoto , entonces esto significa que tienes que construir una clase que contega una List?
Así es, podría sugerir eso
class PhotosList {
  final List photos;

  PhotosList({
    this.photos,
  });
}
También note que, este string es una Lista de maps. Entonces, en nuestro método factory, no tendremos un parámetro Map, porque esto es una Lista. Y eso es exactamente porqué es importante identificar la estructura primero. Luego, nuestro nuevo parámetro podría ser un List.
factory PhotosList.fromJson(List<dynamic> parsedJson) {

    List photos = new List();

    return new PhotosList(
       photos: photos,
    );
  }
Esto podría lanzar un error
Invalid value: Valid value range is empty: 0
Hey, porque nosotros nunca podríamos usar el método Photo.fromJson.
Porqué, si nosotros añadimos esta línea de código después de nuestra inicialización de la lista?
photos = parsedJson.map((i)=>Photo.fromJson(i)).toList();
El mismo concepto que antes, simplemente no tenemos que asignar esto a ninguna clave de la cadena json, porque es una Lista, no un mapa. Código aquí.

Estructura JSON #6 : Estructuras anidadas complejas

Aquí está Page.json.
Te pediré que resuelvas esto. Ya está incluido en el proyecto de muestra. Solo tienes que construir el modelo y el archivo de servicios para esto. Pero no concluiré antes de darte sugerencias y consejos (si es el caso, necesitas alguno).
La regla # 1 y la regla # 2 como de costumbre se aplican. Identificar primero la estructura. Aquí hay un map. Así que todas las estructuras de json del 1 al 5 ayudarán.
La regla # 3 le pide que primero cree las clases y los constructores, y luego agregue los métodos de fábrica desde el nivel inferior. Sólo otro consejo. También agregue las clases desde el nivel profundo / inferior. Por ejemplo, para esta estructura json, cree la clase para Image primero, luego Data yAuthor y luego la clase principal Page. Y agrega los métodos factory también en la misma secuencia.
Para las clases Image yData refiérase Estructura Json #4.
Para la clase Author refiérase a Estructura Json #3.
Tip para principiantes: Mientras experimenta con cualquier nuevo assets, recuerde declararlo en el archivo pubspec.yaml.
Y eso es todo para este artículo de Flutter. Es posible que este artículo no sea el mejor artículo de análisis JSON que existe (porque todavía estoy aprendiendo mucho), pero espero que haya comenzado.

No hay comentarios.:

Publicar un comentario