Deserializing JSON with multiple possible values with rust - json

So i am writing a program in Rust (which I am very new to), that reads a json configuration file and does some stuff depending on the input. I have managed to parse the json successfully using serde_json. The next thing i want to allow the user to do is to be able to specify some advanced options but i don't know how to parse the input.
The default json would look something like this:
{
value: true
}
Parsing this is straight forward to a struct like this:
#[derive(Deserialize)]
pub struct Config {
value: bool
}
How would i go about adding the option for the user to be able to input either a bool or an object as such:
{
value: {
avanced_value: true
}
}
I have tried using an enum like this but it seems bool can not be used within an enum.
#[derive(Deserialize)]
pub struct Config {
value: ValueEnum
}
#[derive(Deserialize)]
pub enum ValueEnum {
bool,
Config(ValueConfig),
}
#[derive(Deserialize)]
pub struct ValueConfig {
advanced_value: bool
}
Am I missing something obvious or should I restructure the input json?
Tnx in advance.

You didn't wrap the bool in an enum variant (like you did with ValueConfig). Also by default, serde tags enums, which is probably not what you want. You want to use an untagged enum:
#[derive(Deserialize)]
pub struct Config {
value: ValueEnum
}
#[derive(Deserialize)]
#[serde(untagged)]
pub enum ValueEnum {
Bool(bool),
Config(ValueConfig),
}
#[derive(Deserialize)]
pub struct ValueConfig {
advanced_value: bool
}

Related

deserializing serde_json from API in rust

I am attempting to scrape the JSON from this endpoint https://prices.runescape.wiki/api/v1/osrs/latest .
#[derive(Serialize, Deserialize, Debug)]
struct Latest {
high: u32,
highTime: String,
low: String,
lowTime: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct Data {
#[serde(with = "serde_with::json::nested")]
data: Vec<Latest>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Datav2 {
#[serde(with = "serde_with::json::nested")]
data: HashMap<u32, Vec<Latest>>,
}
#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let res = reqwest::get(url).await?;
let response = &res.json::<Datav2>().await?;
}
I've tried two versions of the datastructure. Data is using a vector of latest, but i noticed each object has a unique ID, so in DataV2 i tried using a hashmap but i get the same error. I've also tried unnested versions without using Serde_with.
I get the error Error: reqwest::Error { kind: Decode, source: Error("invalid type: map, expected valid json object", line: 1, column: 8)
It seems my datastructure is messed up, but have been trying for hours to figure out the correct data structure to use.
There are multiple issues with your current code.
Datav2 is closer, but still not correct. It is not a HashMap<u32, Vec<Latest>>but a HashMap<u32, Latest>. There is no need to have another Vec, since each number is assigned a value in the JSON.
highTime, low, lowTime are not of type String (since they have no quotes in the JSON), but are unsigned numbers (to be on the safe side I just assumed them to be u64).
Apparently the fields of Latest can be null, so they need to be Options.
I would still use snake_case for the field names in the structs and then rename them to camelCase using the serde macro
I modified your code like I would do this, in order to give you an example of how it could be done:
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Latest {
high: Option<u64>,
high_time: Option<u64>,
low: Option<u64>,
low_time: Option<u64>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Data {
data: HashMap<u64, Latest>,
}
#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let url = "https://prices.runescape.wiki/api/v1/osrs/latest";
let res = reqwest::get(url).await?;
let response = &res.json::<Data>().await?;
println!("{:#?}", response);
Ok(())
}

Is there a way to derive a struct like with Deserialize to get automatic transform from serde_json::Value?

Deserialising from a string directly into a struct works perfectly. But in some cases, you may already have a serde_json::Value in your hands, and want to try and convert it into a struct.
The following example illustrate just that: loading a Request struct from JSON (in a network library for example), with a type string and a generic content as a Value, and then you want to call a handler (from a client library) with the value transformed into a given struct.
use serde::Deserialize;
use serde_json::{json, Value};
use std::convert::TryFrom;
use std::error::Error;
#[derive(Deserialize)]
struct Request {
#[serde(alias = "type")]
req_type: String,
content: Value
}
#[derive(Deserialize)]
struct Person {
name: String,
age: u8
}
// It there a way to avoid having to declare this???
impl TryFrom<Value> for Person {
type Error = serde_json::Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
Person::deserialize(value)
}
}
fn say_hello(p: Person) {
println!("Hello, I'm {}, and I'm {} years old!", p.name, p.age);
}
fn main() -> Result<(), Box<dyn Error>> {
let req: Request = Request::deserialize(json!({
"type": "sayHello",
"content": {
"name": "Pierre",
"age": 32
}
}))?;
match req.req_type.as_str() {
"sayHello" => say_hello(req.content.try_into()?),
_ => println!("unknown request")
}
Ok(())
}
So the question is: is there some derive or other magic implemented which would allow the same behaviour as Deserialize from String, so that the client can only write:
#[derive(Deserialize)]
struct Person {
name: String,
age: u8
}
fn say_hello(p: Person) {
println!("Hello, I'm {}, and I'm {} years old!", p.name, p.age);
}
I tried the #[serde(try_from = "Value")] attribute but it does not look like it's intended for that purpose...
There is serde_json::from_value() specifically for this:
pub fn from_value<T>(value: Value) -> Result<T, Error>
where
T: DeserializeOwned,
Given any serde_json::Value and some T: DeserializedOwned, the function will deserialize the Value to that T, if possible.

Cannot retrieve struct from JSON in Rust

I'm having problems obtaining a struct from reading a JSON file.
This is how the config file looks like:
config.json
{
"config_group_1":{
"variable_1": 10
}
}
Its struct is define as:
pub struct ConfigGroup1 {
variable_1: usize
}
pub struct MainConfig {
config_group_1: ConfigGroup1
}
I'm reading the file with a function that returns String in config_content.
let config: Result = serde_json::from_str(&config_content);
But the compiler is pointing to the following error:
missing generics for type alias `serde_json::Result`
expected 1 type argument
help: use angle brackets to add missing type argument: `<T>`rustc(E0107)
How can I solve this? Where to define that MainConfig is the expected type?
It seems that a lot is missing. I suggest you read the following documentation https://docs.serde.rs/serde_json/
Below is a version that might help you
use serde::{Deserialize, Serialize};
use serde_json::Result;
#[derive(Serialize, Deserialize)]
struct MainConfig {
config_group_1: ConfigGroup1
}
#[derive(Serialize, Deserialize)]
pub struct ConfigGroup1 {
variable_1: usize
}
fn main() -> Result<()> {
let data = r#"{"config_group_1": {"variable_1": 10 }}"#;
let p: MainConfig = serde_json::from_str(data).expect("JSON was not well-formatted");
println!("Value {} ", p.config_group_1.variable_1);
Ok(())
}

How can I do key-agnostic deserialization of JSON objects?

I am working with a less than ideal API that doesn't follow any rigid standard for sending data. Each payload comes with some payload info before the JSON, followed by the actual data inside which can be a single string or several fields.
As it stands right now, if I were to map every different payload to a struct I would end up with roughly 50 structs. I feel like this is not ideal, because a ton of these structs overlap in all but key. For instance, there are I believe 6 different versions of payloads that could be mapped to something like the following, but they all have different keys.
I have these two JSON examples:
{"key": "string"}
{"key2": "string"}
And I want to serialize both into this struct:
#[derive(Debug, Deserialize)]
struct SimpleString {
key: String,
}
The same can be said for two strings, and even a couple cases for three. The payloads are frustratingly unique in small ways, so my current solution is to just define the structs locally inside the function that deserializes them and then pass that data off wherever it needs to go (in my case a cache and an event handler)
Is there a better way to represent this that doesn't have so much duplication? I've tried looking for things like key-agnostic deserializing but I haven't found anything yet.
You can implement Deserialize for your type to decode a "map" and ignore the key name:
extern crate serde;
extern crate serde_json;
use std::fmt;
use serde::de::{Deserialize, Deserializer, Error, MapAccess, Visitor};
fn main() {
let a = r#"{"key": "string"}"#;
let b = r#"{"key2": "string"}"#;
let a: SimpleString = serde_json::from_str(a).unwrap();
let b: SimpleString = serde_json::from_str(b).unwrap();
assert_eq!(a, b);
}
#[derive(Debug, PartialEq)]
struct SimpleString {
key: String,
}
struct SimpleStringVisitor;
impl<'de> Visitor<'de> for SimpleStringVisitor {
type Value = SimpleString;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an object with a single string value of any key name")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
if let Some((_, key)) = access.next_entry::<String, _>()? {
if access.next_entry::<String, String>()?.is_some() {
Err(M::Error::custom("too many values"))
} else {
Ok(SimpleString { key })
}
} else {
Err(M::Error::custom("not enough values"))
}
}
}
impl<'de> Deserialize<'de> for SimpleString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(SimpleStringVisitor)
}
}

rustc-serialize custom enum decoding

I have a JSON structure where one of the fields of a struct could be either an object, or that object's ID in the database. Let's say the document looks like this with both possible formats of the struct:
[
{
"name":"pebbles",
"car":1
},
{
"name":"pebbles",
"car":{
"id":1,
"color":"green"
}
}
]
I'm trying to figure out the best way to implement a custom decoder for this. So far, I've tried a few different ways, and I'm currently stuck here:
extern crate rustc_serialize;
use rustc_serialize::{Decodable, Decoder, json};
#[derive(RustcDecodable, Debug)]
struct Car {
id: u64,
color: String
}
#[derive(Debug)]
enum OCar {
Id(u64),
Car(Car)
}
#[derive(Debug)]
struct Person {
name: String,
car: OCar
}
impl Decodable for Person {
fn decode<D: Decoder>(d: &mut D) -> Result<Person, D::Error> {
d.read_struct("root", 2, |d| {
let mut car: OCar;
// What magic must be done here to get the right OCar?
/* I tried something akin to this:
let car = try!(d.read_struct_field("car", 0, |r| {
let r1 = Car::decode(r);
let r2 = u64::decode(r);
// Compare both R1 and R2, but return code for Err() was tricky
}));
*/
/* And this got me furthest */
match d.read_struct_field("car", 0, u64::decode) {
Ok(x) => {
car = OCar::Id(x);
},
Err(_) => {
car = OCar::Car(try!(d.read_struct_field("car", 0, Car::decode)));
}
}
Ok(Person {
name: try!(d.read_struct_field("name", 0, Decodable::decode)),
car: car
})
})
}
}
fn main() {
// Vector of both forms
let input = "[{\"name\":\"pebbles\",\"car\":1},{\"name\":\"pebbles\",\"car\":{\"id\":1,\"color\":\"green\"}}]";
let output: Vec<Person> = json::decode(&input).unwrap();
println!("Debug: {:?}", output);
}
The above panics with an EOL which is a sentinel value rustc-serialize uses on a few of its error enums. Full line is
thread '<main>' panicked at 'called `Result::unwrap()` on an `Err` value: EOF', src/libcore/result.rs:785
What's the right way to do this?
rustc-serialize, or at least its JSON decoder, doesn't support that use case. If you look at the implementation of read_struct_field (or any other method), you can see why: it uses a stack, but when it encounters an error, it doesn't bother to restore the stack to its original state, so when you try to decode the same thing differently, the decoder is operating on an inconsistent stack, eventually leading to an unexpected EOF value.
I would recommend you look into Serde instead. Deserializing in Serde is different: instead of telling the decoder what type you're expecting, and having no clear way to recover if a value is of the wrong type, Serde calls into a visitor that can handle any of the types that Serde supports in the way it wants. This means that Serde will call different methods on the visitor depending on the actual type of the value it parsed. For example, we can handle integers to return an OCar::Id and objects to return an OCar::Car.
Here's a full example:
#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]
extern crate serde;
extern crate serde_json;
use serde::de::{Deserialize, Deserializer, Error, MapVisitor, Visitor};
use serde::de::value::MapVisitorDeserializer;
#[derive(Deserialize, Debug)]
struct Car {
id: u64,
color: String
}
#[derive(Debug)]
enum OCar {
Id(u64),
Car(Car),
}
struct OCarVisitor;
#[derive(Deserialize, Debug)]
struct Person {
name: String,
car: OCar,
}
impl Deserialize for OCar {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> where D: Deserializer {
deserializer.deserialize(OCarVisitor)
}
}
impl Visitor for OCarVisitor {
type Value = OCar;
fn visit_u64<E>(&mut self, v: u64) -> Result<Self::Value, E> where E: Error {
Ok(OCar::Id(v))
}
fn visit_map<V>(&mut self, visitor: V) -> Result<Self::Value, V::Error> where V: MapVisitor {
Ok(OCar::Car(try!(Car::deserialize(&mut MapVisitorDeserializer::new(visitor)))))
}
}
fn main() {
// Vector of both forms
let input = "[{\"name\":\"pebbles\",\"car\":1},{\"name\":\"pebbles\",\"car\":{\"id\":1,\"color\":\"green\"}}]";
let output: Vec<Person> = serde_json::from_str(input).unwrap();
println!("Debug: {:?}", output);
}
Output:
Debug: [Person { name: "pebbles", car: Id(1) }, Person { name: "pebbles", car: Car(Car { id: 1, color: "green" }) }]
Cargo.toml:
[dependencies]
serde = "0.7"
serde_json = "0.7"
serde_macros = "0.7"