Reputation: 2402
I have a nested JSON structure, the relevant bits of which look like this:
{
"params": {
"foo": {
"type": "integer"
},
"bar": {
"type": "integer",
"choices": [
0,
26,
4
]
},
"foobar": {
"type": "decimal"
},
"foobaz": {
"type": "decimal",
"choices": [
0.1,
26.6,
4
]
}
}
}
Or, in other words, a map of objects, each of which specifies a field containing its 'type' (integer or decimal), and an optional array of choices.
I am trying to deserialise this into a rust struct like this:
#[derive(Debug)]
pub struct OrderParameter<S> {
choices: Option<Vec<S>>,
}
Where S
is either a rust Decimal
(from rust_decimal
) or an isize
. i.e., the parameters should end up looking like this (pseudocode)
foo: OrderParameter<isize> {choices: None}
bar: OrderParameter<isize> {choices: Some(Vec(0_i, 26_i,...)}
foobar: OrderParameter<Decimal> {choices: None}
foobaz: OrderParameter<Decimal> {choices: Some(Vec(Decimal(0,1)...)}
I have been referring to this: https://serde.rs/deserialize-struct.html But I'm struggling to get the generic part to work. This is what I have so far:
use super::errors::ParseError;
use rust_decimal::prelude::*;
use serde::{de, de::MapAccess, de::SeqAccess, de::Visitor, Deserialize, Deserializer, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
#[derive(PartialEq)]
pub enum OrderParameterType {
Integer,
Decimal,
}
impl FromStr for OrderParameterType {
type Err = ParseError;
fn from_str(v: &str) -> Result<Self, Self::Err> {
match v {
"integer" => Ok(Self::Integer),
"decimal" => Ok(Self::Decimal),
_ => Err(ParseError),
}
}
}
#[derive(Debug)]
pub struct OrderParameter<S> {
choices: Option<Vec<S>>,
}
impl<'de, S> Deserialize<'de> for OrderParameter<S> { // Compiler suggests: consider restricting type parameter `S`: `: parser::types::participant::_::_serde::Deserialize<'_>`, but adding S: Deserialize<'de> to the where block results in: impl has stricter requirements than trait impl has extra requirement `S: Deserialize<'de>`
fn deserialize<D>(deserializer: D) -> Result<OrderParameter<S>, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
Variant,
Choices,
}
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`type` or `choices`")
}
fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: de::Error,
{
match value {
"type" => Ok(Field::Variant),
"choices" => Ok(Field::Choices),
_ => Err(de::Error::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct OrderParameterVisitor<T> {
marker: PhantomData<fn() -> OrderParameter<T>>,
}
impl<T> OrderParameterVisitor<T> {
fn new() -> Self {
Self {
marker: PhantomData,
}
}
}
impl<'de, T> Visitor<'de> for OrderParameterVisitor<T>
where
T: Deserialize<'de>,
{
type Value = OrderParameter<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct OrderParameter")
}
fn visit_map<V>(self, mut map: V) -> Result<OrderParameter<T>, V::Error>
where
V: MapAccess<'de>,
{
let mut variant = None;
let mut choices: Option<Vec<T>>;
while let Some(key) = map.next_key()? {
match key {
Field::Variant => {
if variant.is_some() {
return Err(de::Error::duplicate_field("type"));
}
variant =
Some(OrderParameterType::from_str(map.next_value()?).unwrap());
}
Field::Choices => {
if choices.is_some() {
return Err(de::Error::duplicate_field("choices"));
}
choices = Some(map.next_value()?);
}
}
}
let variant = variant.ok_or_else(|| de::Error::missing_field("type"))?;
if choices.is_some() {
if variant == OrderParameterType::Decimal {
let mapped_choices: Vec<Decimal>;
mapped_choices = Some(choices)
.iter()
.map(|x| Decimal::from_str(x).unwrap())
.collect::<Vec<Decimal>>()
} else if variant == OrderParameterType::Integer {
let mapped_choices: Vec<isize> = Vec::from([0, 1]);
} else if variant == OrderParameterType::Wallet {
choices = None
} else if variant == OrderParameterType::Participant {
choices = None
}
}
Ok(OrderParameter { choices })
}
}
const FIELDS: &'static [&'static str] = &["type", "choices"];
deserializer.deserialize_struct("OrderParameter", FIELDS, OrderParameterVisitor::new())
}
}
Upvotes: 0
Views: 934
Reputation: 13750
Are you sure you really want generics? I think you might actually want similarly structured enum variants. Remarkably, you need neither generics nor custom (de)serialization logic for this — serde on its own contains all the machinery you need to derive the relevant traits.
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Serialize, Deserialize)]
struct Params {
params: BTreeMap<String, Item>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
enum Item {
Integer {
#[serde(default)]
choices: Option<Vec<i32>>,
},
Decimal {
#[serde(default)]
choices: Option<Vec<Decimal>>,
},
}
fn main() {
let json = r#"
{
"params": {
"foo": {
"type": "integer"
},
"bar": {
"type": "integer",
"choices": [
0,
26,
4
]
},
"foobar": {
"type": "decimal"
},
"foobaz": {
"type": "decimal",
"choices": [
0.1,
26.6,
4
]
}
}
}"#;
let items = serde_json::from_str::<Params>(json).unwrap().params;
println!("{:#?}", items);
}
Which gets you
{
"bar": Integer {
choices: Some(
[
0,
26,
4,
],
),
},
"foo": Integer {
choices: None,
},
"foobar": Decimal {
choices: None,
},
"foobaz": Decimal {
choices: Some(
[
0.1,
26.6,
4,
],
),
},
}
Upvotes: 1