Leer y escribir JSON en Rust con Serde
30/12/2017
JSON es un formato muy popular para guardar y transmitir información. En Rust podemos leer y escribir JSON de forma transparente gracias a Serde. Serde es una librería para Rust, que permite transformar las structs nativas de Rust en ficheros JSON, YAML, BSON, MsgPack, XML y viceversa. Serde está compuesto de varios plugins, uno para cada formato de archivo, así que vamos a necesitar el plugin de JSON. Es lo que se dice serializar (guardar) y deserializar (leer).
Para deserializar necesitamos primero definir el struct en Rust que se corresponde al JSON que vamos a cargar. Además, hay que incluir una cláusula derive e indicar que derivamos Deserializable.
Una vez hecho eso podemos realizar la transformación con Serde. Serde admite varios métodos de entrada. En mi caso voy a usar from_reader, que permite transformar desde cualquier std::io::Read. También se podría usar from_str, que permite leer desde un String.
Así pues un programa que lea Landmarks de un fichero JSON como este:
Quedaría así:
Nótese como usamos Vec<Landmark> para indicar que vamos a cargar un JSON que es un array de Landmarks.
Ahora si queremos generar un archivo JSON es muy sencillo. Simplemente marcamos Serialize en la misma estructura que queramos serializar. Posteriormente en Serde podemos utilizar diferentes métodos, como to_writer o to_string.
Serde permite a través de atributos, definir de forma precisa que ocurre con los elementos. En caso de no poner nada, por ejemplo, Serde usará los nombres del struct de Rust en los ficheros y si hay elementos de más, en el fichero al leer, los ignorará.
Por ejemplo, si queremos que en el fichero JSON, en vez de tener name sea nombre, podemos usar rename.
Podemos también no querer guardar algún elemento del struct, usamos skip.
Si queremos que Serde falle en caso de que en el JSON haya más campos de los que hemos definido, usamos deny_unkown_fields.
Y si queremos que el nombre del struct sea distinto al que crea oportuno Serde, podemos redefinirlo también:
Por último, mencionar que Serde permite serializar/deserializar cosas más complejas, con otros structs, con vectores, con HashMap, entre sus elementos,... Lo único que tendrá que pasar es que esas estructuras a su vez sean serializables/deserializables con Serde (es decir, hayan puesto derive(Serialize,Deserialize)).
Deserializando JSON con Serde
Para deserializar necesitamos primero definir el struct en Rust que se corresponde al JSON que vamos a cargar. Además, hay que incluir una cláusula derive e indicar que derivamos Deserializable.
#[derive(Deserialize)]
struct Landmark {
x: i32,
y: i32,
name: String
}
Una vez hecho eso podemos realizar la transformación con Serde. Serde admite varios métodos de entrada. En mi caso voy a usar from_reader, que permite transformar desde cualquier std::io::Read. También se podría usar from_str, que permite leer desde un String.
Así pues un programa que lea Landmarks de un fichero JSON como este:
[{
"x": 42,
"y" : -1,
"name" : "Presa de Tibi"
},
{
"x" : 50,
"y" : 23,
"name" : "Rollo de Villalón de Campos"
}]
Quedaría así:
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
use std::io::BufReader;
use std::fs::File;
#[derive(Deserialize)]
struct Landmark {
x: i32,
y: i32,
name: String
}
fn main() {
let file = File::open("landmarks.json").unwrap();
let reader = BufReader::new(file);
let landmarks: Vec<Landmark> = serde_json::from_reader(reader).unwrap();
for landmark in landmarks{
println!("Landmark name: {}\tPosition: ({},{})",landmark.name,landmark.x,landmark.y);
}
}
Nótese como usamos Vec<Landmark> para indicar que vamos a cargar un JSON que es un array de Landmarks.
Serializar JSON
Ahora si queremos generar un archivo JSON es muy sencillo. Simplemente marcamos Serialize en la misma estructura que queramos serializar. Posteriormente en Serde podemos utilizar diferentes métodos, como to_writer o to_string.
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
use std::io::{BufReader,BufWriter};
use std::fs::File;
#[derive(Serialize, Deserialize)]
struct Landmark {
x: i32,
y: i32,
name: String
}
fn main() {
let landmark = Landmark {
x: 67,
y: 23,
name: String::from("Academia de Caballería")
};
let output = File::create("caballeria.json").unwrap();
let writer = BufWriter::new(output);
serde_json::to_writer(writer,&landmark).unwrap();
}
Personalizando la serialización y la deserialización
Serde permite a través de atributos, definir de forma precisa que ocurre con los elementos. En caso de no poner nada, por ejemplo, Serde usará los nombres del struct de Rust en los ficheros y si hay elementos de más, en el fichero al leer, los ignorará.
Por ejemplo, si queremos que en el fichero JSON, en vez de tener name sea nombre, podemos usar rename.
#[derive(Serialize, Deserialize)]
struct Landmark {
x: i32,
y: i32,
#[serde(rename = "nombre")]
name: String
}
Podemos también no querer guardar algún elemento del struct, usamos skip.
#[derive(Serialize, Deserialize)]
struct Landmark {
x: i32,
y: i32,
name: String,
#[serde(skip)]
id: u32
}
Si queremos que Serde falle en caso de que en el JSON haya más campos de los que hemos definido, usamos deny_unkown_fields.
#[derive(Serialize, Deserialize, Debug)]
#[serde(deny_unknown_fields)]
struct Landmark {
x: i32,
y: i32,
name: String,
}
Y si queremos que el nombre del struct sea distinto al que crea oportuno Serde, podemos redefinirlo también:
#[derive(Serialize, Deserialize)]
#[serde(rename = "landmark")]
struct Landmark {
x: i32,
y: i32,
name: String,
}
Por último, mencionar que Serde permite serializar/deserializar cosas más complejas, con otros structs, con vectores, con HashMap, entre sus elementos,... Lo único que tendrá que pasar es que esas estructuras a su vez sean serializables/deserializables con Serde (es decir, hayan puesto derive(Serialize,Deserialize)).