Post Top Ad

Your Ad Spot

martes, 9 de julio de 2019

Lectura y escritura JSON en Java

¿Qué es JSON?

La notación de objetos de JavaScript o, en resumen, JSON es un formato de intercambio de datos que se introdujo en 1999 y se adoptó ampliamente a mediados de la década de 2000. Actualmente, es el formato estándar de facto para la comunicación entre los servicios web y sus clientes (navegadores, aplicaciones móviles, etc.). Saber leer y escribir es una habilidad esencial para cualquier desarrollador de software.
Aunque JSON se derivó de JavaScript, es un formato independiente de la plataforma. Puedes trabajar con él en múltiples lenguajes de programación, incluyendo Java, Python, Ruby y muchos más. Realmente, cualquier lenguaje que pueda analizar una cadena puede manejar JSON.
La popularidad de JSON resultó en su soporte nativo en muchas bases de datos, las últimas versiones de PostgreSQL y MySQL contienen el soporte nativo para consultar los datos almacenados en los campos JSON. Las bases de datos NoSQL como MongoDB se construyeron sobre este formato y utilizan documentos JSON para almacenar registros, al igual que las tablas y filas almacenan registros en una base de datos relacional.
Una de las principales ventajas de JSON, en comparación con el formato de datos XML, es el tamaño del documento. Como JSON no tiene esquemas, no es necesario llevar una sobrecarga estructural masiva como espacios de nombres y envoltorios.
JSON es un formato de datos genérico que tiene seis tipos de datos:
  • Instrumentos de cuerda
  • Números
  • Booleanos
  • Arrays
  • Objetos
  • nulo
Echemos un vistazo a un simple documento JSON:
{
  "name": "Benjamin Watson",
  "age": 31,
  "isMarried": true,
  "hobbies": ["Football", "Swimming"],
  "kids": [
    {
      "name": "Billy",
      "age": 5
    }, 
   {
      "name": "Milly",
      "age": 3
    }
  ]
}
Esta estructura define un objeto que representa a una persona llamada "Benjamin Watson". Podemos ver sus detalles aquí, como su edad, estado familiar y pasatiempos.
En esencia, el objeto JSON no es más que una cadena. Una cadena que representa un objeto, razón por la cual los objetos JSON a menudo se llaman cadenas JSON o documentos JSON .

json-simple

Como no hay soporte nativo para JSON en Java, en primer lugar, debemos agregar una nueva dependencia que nos la proporcione. Para empezar, usaremos el módulo json-simple , y lo agregaremos como una dependencia de Maven.
<dependency>  
    <groupId>com.googlecode.json-simple</groupId>
    <artifactId>json-simple</artifactId>
    <version>{version}</version>
</dependency>  
Este módulo es totalmente compatible con la especificación JSON RFC4627 y proporciona una funcionalidad central como la codificación y decodificación de objetos JSON y no tiene dependencias de módulos externos.
Vamos a crear un método simple que tome un nombre de archivo como parámetro y escriba algunos datos JSON codificados:
public static void writeJsonSimpleDemo(String filename) throws Exception {  
    JSONObject sampleObject = new JSONObject();
    sampleObject.put("name", "Stackabuser");
    sampleObject.put("age", 35);

    JSONArray messages = new JSONArray();
    messages.add("Hey!");
    messages.add("What's up?!");

    sampleObject.put("messages", messages);
    Files.write(Paths.get(filename), sampleObject.toJSONString().getBytes());
}
Aquí, estamos creando una instancia de la JSONObjectclase, poniendo un nombre y una edad como propiedades. Luego estamos creando una instancia de la clase que JSONArraysuma dos elementos de cadena y la colocamos como una tercera propiedad de nuestro sampleObjectEn última instancia, nos estamos transformando sampleObjecten un documento JSON que llama al toJSONString()método y lo escribe en un archivo.
Para ejecutar este código, debemos crear un punto de entrada a nuestra aplicación que podría tener este aspecto:
public class Solution {  
    public static void main(String[] args) throws Exception {
        writeJsonSimpleDemo("example.json");
    }
}
Como resultado de la ejecución de este código, obtendremos un archivo nombrado example.jsonen la raíz de nuestro paquete. El contenido del archivo será un documento JSON, con todas las propiedades que hemos incluido:
{"name":"Stackabuser","messages":["Hey!","What's up?!"],"age":35}
¡Genial! Acabamos de tener nuestra primera experiencia con el formato JSON y hemos serializado con éxito un objeto Java en él y lo hemos escrito en el archivo.
Ahora, con una ligera modificación de nuestro código fuente, podemos leer el objeto JSON del archivo e imprimirlo en la consola completamente o imprimir las propiedades individuales seleccionadas:
public static void main(String[] args) throws Exception {  
    JSONObject jsonObject = (JSONObject) readJsonSimpleDemo("example.json");
    System.out.println(jsonObject);
    System.out.println(jsonObject.get("age"));
}

public static Object readJsonSimpleDemo(String filename) throws Exception {  
    FileReader reader = new FileReader(filename);
    JSONParser jsonParser = new JSONParser();
    return jsonParser.parse(reader);
}
Es importante tener en cuenta que el parse()método devuelve Objecty tenemos que hacerlo explícitamente JSONObject.
Si tiene un documento JSON con formato incorrecto o dañado, obtendrá una excepción similar a esta:
Exception in thread "main" Unexpected token END OF FILE at position 64.  
Para simularlo, intente eliminar el último corchete de cierre }.

Cavar más profundo

Aunque json-simplees útil, no nos permite usar clases personalizadas sin escribir código adicional. Supongamos que tenemos una clase que representa a una persona de nuestro ejemplo inicial:
class Person {  
    Person(String name, int age, boolean isMarried, List<String> hobbies,
            List<Person> kids) {
        this.name = name;
        this.age = age;
        this.isMarried = isMarried;
        this.hobbies = hobbies;
        this.kids = kids;
    }

    Person(String name, int age) {
        this(name, age, false, null, null);
    }

    private String name;
    private Integer age;
    private Boolean isMarried;
    private List<String> hobbies;
    private List<Person> kids;

    // getters and setters

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", isMarried=" + isMarried +
                ", hobbies=" + hobbies +
                ", kids=" + kids +
                '}';
    }
}
Tomemos el documento JSON que usamos como ejemplo al principio y lo colocamos en el example.jsonarchivo:
{
  "name": "Benjamin Watson",
  "age": 31,
  "isMarried": true,
  "hobbies": ["Football", "Swimming"],
  "kids": [
    {
      "name": "Billy",
      "age": 5
    }, 
   {
      "name": "Milly",
      "age": 3
    }
  ]
}
Nuestra tarea sería deserializar este objeto de un archivo a una instancia de la Personclase. Intentemos hacer esto usando simple-jsonprimero.
Modificando nuestro main()método, reutilizando la estática readSimpleJsonDemo()y agregando las importaciones necesarias, llegaremos a:
public static void main(String[] args) throws Exception {  
    JSONObject jsonObject = (JSONObject) readJsonSimpleDemo("example.json");
    Person ben = new Person(
                (String) jsonObject.get("name"),
                Integer.valueOf(jsonObject.get("age").toString()),
                (Boolean) jsonObject.get("isMarried"),
                (List<String>) jsonObject.get("hobbies"),
                (List<Person>) jsonObject.get("kids"));

    System.out.println(ben);
}
No se ve muy bien, tenemos muchos pronósticos típicos extraños, pero parece que hace el trabajo, ¿no?
Bueno en realidad no...
Intentemos imprimir en la consola la kidsmatriz de nuestro Persony luego la edad del primer niño.
System.out.println(ben.getKids());  
System.out.println(ben.getKids().get(0).getAge());  
Como vemos, la primera salida de la consola muestra un resultado aparentemente bueno de:
[{"name":"Billy","age":5},{"name":"Milly","age":3}]
pero el segundo arroja un Exception:
Exception in thread "main" java.lang.ClassCastException: org.json.simple.JSONObject cannot be cast to com.stackabuse.json.Person  
El problema aquí es que nuestro encasillado a un List<Person>no creó dos nuevos Personobjetos, simplemente rellenó lo que había allí, uno JSONObjecten nuestro caso actual. Cuando tratamos de profundizar y obtener la edad real del primer niño, nos encontramos con a ClassCastException.
Este es un gran problema que estoy seguro de que podrás superar escribiendo un montón de códigos muy inteligentes de los que podrías estar orgulloso, pero hay una forma sencilla de hacerlo desde el principio.

Jackson

Una biblioteca que nos permitirá hacer todo esto de una manera muy eficiente se llama Jackson . Es muy común y se usa en proyectos de grandes empresas como Hibernate .
Añadámoslo como una nueva dependencia de Maven:
<dependency>  
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>{version}</version>
</dependency>  
La clase principal que usaremos se llama ObjectMapper, tiene un método readValue()que toma dos argumentos: una fuente para leer y una clase para emitir el resultado.
ObjectMapper podría configurarse con una serie de opciones diferentes pasadas al constructor:
FAIL_ON_SELF_REFERENCES Una característica que determina lo que sucede cuando un POJO detecta una auto-referencia directa (y no se habilita el manejo de ID de objeto): se lanza una excepción JsonMappingException (si es verdadera), o la referencia normalmente se procesa (falsa).
INDENT_OUTPUTUna característica que permite habilitar (o deshabilitar) la sangría para el generador subyacente, usando la impresora bonita predeterminada configurada para ObjectMapper (y ObjectWriters creados a partir del asignador).
ORDER_MAP_ENTRIES_BY_KEYESFunción que determina si las entradas del Mapa se ordenan primero por clave antes de la serialización o no: si está habilitada, se realiza un paso de clasificación adicional si es necesario (no es necesario para SortedMaps), si está deshabilitado, no se necesita una clasificación adicional.
USE_EQUALITY_FOR_OBJECT_IDCaracterística que determina si la identidad del objeto se compara utilizando la verdadera identidad del objeto a nivel de JVM (falso); o, es igual al método ().
Una característica que determina cómo se serializa el tipo char []: cuando está habilitada, se serializará como una matriz JSON explícita (con cadenas de un solo carácter como valores); cuando está deshabilitado, el valor predeterminado es serializarlos como cadenas (que es más compacto).
WRITE_DATE_KEYS_AS_TIMESTAMPSUna característica que determina si las fechas (y subtipos) utilizadas como claves de mapas se serializan como marcas de tiempo o no (si no, se serializarán como valores de texto).
WRITE_DATE_TIMESTAMPS_AS_NANOSECONDSUna característica que controla si los valores numéricos de la marca de tiempo se deben escribir utilizando las marcas de tiempo de nanosegundos (habilitado) o no (deshabilitado); Si y solo si el tipo de datos admite dicha resolución.
WRITE_DATES_AS_TIMESTAMPSUna característica que determina si los valores de Fecha (y fecha / hora) (y los elementos basados ​​en Fecha como Calendarios) se deben serializar como marcas de tiempo numéricas (verdadero; el valor predeterminado), o como otra cosa (generalmente representación textual).
WRITE_DATES_WITH_ZONE_IDUna característica que determina si los valores de fecha / fecha y hora deben ser serializados para que incluyan el identificador de zona horaria, en los casos en que el propio tipo contenga información de zona horaria.
Una lista completa de la SerializationFeatureenumeración está disponible aquí .
public static void main(String[] args) throws Exception {  
    ObjectMapper objectMapper = new ObjectMapper();
    Person ben = objectMapper.readValue(new File("example.json"), Person.class);
    System.out.println(ben);
    System.out.println(ben.getKids());
    System.out.println(ben.getKids().get(0).getAge());
}
Desafortunadamente, después de ejecutar este fragmento de código, obtendremos una excepción:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com.stackabuse.json.Person]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)  
Por lo que parece, tenemos que agregar el constructor predeterminado a la Personclase:
public Person() {}  
Al volver a ejecutar el código, veremos otra excepción emergente:
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "isMarried" (class com.stackabuse.json.Person), not marked as ignorable (5 known properties: "hobbies", "name", "married", "kids", "age"])  
Este es un poco más difícil de resolver ya que el mensaje de error no nos dice qué hacer para lograr el resultado deseado. Ignorar la propiedad no es una opción viable, como lo tenemos claramente en el documento JSON y queremos que se traduzca al objeto Java resultante.
El problema aquí está relacionado con la estructura interna de la biblioteca Jackson. Deriva los nombres de propiedades de los captadores, eliminando las primeras partes de ellos. En el caso de getAge()getName()funciona perfectamente, pero con isMarried()esto no funciona y asume que el campo debe ser llamado en marriedlugar de isMarried.
Una opción brutal, pero de trabajo: podemos resolver este problema simplemente cambiando el nombre del que lo recibe isIsMarriedSigamos adelante y tratemos de hacer esto.
¡No aparecen más excepciones, y vemos el resultado deseado!
Person{name='Benjamin Watson', age=31, isMarried=true, hobbies=[Football, Swimming], kids=[Person{name='Billy', age=5, isMarried=null, hobbies=null, kids=null}, Person{name='Milly', age=3, isMarried=null, hobbies=null, kids=null}]}

[Person{name='Billy', age=5, isMarried=null, hobbies=null, kids=null}, Person{name='Milly', age=3, isMarried=null, hobbies=null, kids=null}]

5  
Aunque el resultado es satisfactorio, hay una mejor manera de evitar esto que agregar otra isa cada uno de sus captadores booleanos.
Podemos lograr el mismo resultado agregando una anotación al isMarried()método:
@JsonProperty(value="isMarried")
public boolean isMarried() {  
    return isMarried;
}
De esta manera le estamos diciendo explícitamente a Jackson el nombre del campo y no tiene que adivinar. Podría ser especialmente útil en los casos en que el campo se denomina totalmente diferente de los captadores.

Conclusión

JSON es un formato ligero basado en texto que nos permite representar objetos y transferirlos a través de la web o almacenar en la base de datos.
No hay soporte nativo para la manipulación JSON en Java, sin embargo, hay varios módulos que proporcionan esta funcionalidad. En este tutorial, hemos cubierto los módulos json-simpleJackson, mostrando las fortalezas y debilidades de cada uno de ellos.
Al trabajar con JSON, debe tener en cuenta los matices de los módulos con los que está trabajando y depurar las excepciones que podrían aparecer con cuidado.

No hay comentarios.:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

outbrain

Páginas