4
\$\begingroup\$

I added an enum for my webscraper to deserialize data from a JSON field that represents an HTML image size, which can either be an unsigned int like 1080 or a string like "100%":

use serde::Deserialize;
use std::fmt::{Display, Formatter};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum HtmlSize {
 String(String),
 Int(u64),
}
impl Display for HtmlSize {
 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
 match self {
 Self::String(string) => write!(f, r#""{}""#, string),
 Self::Int(int) => write!(f, "{}", int),
 }
 }
}
impl<'de> Deserialize<'de> for HtmlSize {
 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 where
 D: serde::Deserializer<'de>,
 {
 deserializer.deserialize_any(HtmlSizeVisitor)
 }
}
struct HtmlSizeVisitor;
impl<'de> serde::de::Visitor<'de> for HtmlSizeVisitor {
 type Value = HtmlSize;
 fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
 write!(
 formatter,
 "an unsigned integer or a string representing a width"
 )
 }
 fn visit_u64<E: serde::de::Error>(self, n: u64) -> Result<HtmlSize, E> {
 Ok(HtmlSize::Int(n))
 }
 fn visit_str<E: serde::de::Error>(self, string: &str) -> Result<HtmlSize, E> {
 Ok(HtmlSize::String(string.to_string()))
 }
}

Example usage:


#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct ImageInfo {
 id: String,
 alt: String,
 height: HtmlSize,
 src: String,
 width: HtmlSize,
 caption: String,
 credit: String,
}

I did not find anything readily available like this, so I implemented the above HtmlSize with its corresponding visitor. Is this a sensible implementation or did I reinvent the wheel? Can the implementation be improved?

asked Jun 20, 2023 at 19:17
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

You don't actually need to implement your own logic, you can use #[serde(untagged):

#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
#[serde(untagged)]
pub enum HtmlSize {
 String(String),
 Int(u64),
}

#[serde(untagged)] will deserialize into which enum variant matches the incoming data.

answered Jun 20, 2023 at 21:38
\$\endgroup\$
0
4
\$\begingroup\$

I don't have enough experience with rust in particular to usefully comment on the implementation itself

Still, there's one part of the design that I do think I can talk about - I expect that an image with width: "hello" is not, and should never be, considered valid. Yet an HtmlSize appears to be defined to be any u64 or String

In the interest of avoiding invalid values and clarifying the meanings of valid values, I'd personally go for a slightly more opinionated approach, with more explicit names, rejecting values that don't make sense as sizes. Going by your examples, I'd lean towards giving HtmlSize variants like Pixels(u64) (which would get serialized to a number), Percentage(u64) (where HtmlSize::Percentage(50) would get serialized as "50%") and perhaps more options depending on what other kinds of values we expect to see (Em(u64)? Variants for CSS keywords like max-content, auto, or inherit? Other stuff entirely?)

answered Jun 20, 2023 at 20:37
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thank you. These are really good ideas. I don't need those fine-grained options at the moment, since the JSON provider currenty guarantes valid values, but I will consider implementing this as soon as I need it. \$\endgroup\$ Commented Jun 20, 2023 at 21:17

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.