Can I create enums in rust that have tuples as values? - rust

Can I have a tuple as value in enums? Basically I want this in order to use an integer value as a database input and a string value as a friendly response to a UI caller.
For example:
#[derive(Deserialize, Debug)]
enum MyTestType {
A(0, "Default"),
B(1, "something else"),
C(18, "18"),
D(4, "D")
}
I am using serde crate in rust and it would be convenient to have it in order to avoid a struct here

Of course:
use serde::Serialize;
use serde_json;
#[derive(Serialize)]
enum Test {
A(u32, String), // NOT a tuple
B((u32, String)) // actual tuple
}
fn main () {
let a = Test::A(15, "Hello".to_string());
let b = Test::B((42, "Hi".to_string()));
println!("{}", serde_json::to_string(&a).unwrap());
println!("{}", serde_json::to_string(&b).unwrap())
}
Output:
{"A":[15,"Hello"]}
{"B":[42,"Hi"]}

Related

Rust - deserialization using serde/reqwest "Invalid type"

I'm trying to deserialize the following API response (for simplicity, I'll only copy two slices of the array, but it will be bigger in reality). The code is oversimplified to demonstrate the example.
API response:
[[1609632000000,32185,32968,34873,31975,18908.90248876],[1609545600000,29349.83250154,32183,33292,29000,22012.92431526]]
So it's a big array/vector, composed of arrays/vectors with six integer OR floats (their position will also vary).
For this, I'm trying to use the generics , but it seems I'm missing something since I cannot get it to compile..
it is failing with
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: reqwest::Error { kind: Decode, source: Error("invalid type: integer `1609632000000`, expected struct T", line: 1, column: 15) }'
use blocking::Response;
use serde::{Deserialize, Serialize};
use reqwest::{blocking, Error, StatusCode};
struct allData<T> {
data: Slice<T>
}
#[derive(Debug, Serialize, Deserialize)]
struct Slice<T>{
data1: T,
data2: T,
data3: T,
data4: T,
data5: T,
data6: T,
}
fn get_data() -> Option<Slice> /*I know the signature is not correct, read until the end for the correct one*/ {
let url = format!("{}", my_url_string);
let response: Slice<T> = blocking::get(&url).unwrap().json().unwrap();
Some(response);
}
fn main() {
let call_the_api = get_data();
println!("{:?}", call_the_api);
}
What would be the correct way to use a Struct with generics that can return a vector of "Slice".
ie.
Vector{
Slice {
data1,
data2,
data3,
data4,
data5,
data6,
},
Slice {
data1,
data2,
data3,
data4,
data5,
data6,
}
}
The derivation of Deserialize on your Slice struct does not work on a JSON array, instead it expects a JSON dict with fields data1, data2 and so on. Presumably, you don't want to manually implement the Deserialize trait for your type, so you'll want a Vec<Vec<T>> to model your nested arrays:
#[derive(Deserialize)]
#[serde(transparent)]
struct AllData<T>(Vec<Vec<T>>);
This type restricts all items in the Vec to be of type T. This means, you don't get to use some f64 and some i64, it'll either be all floats or all ints. To make this wrapper generic over both floats and ints, you'll probably want some enum:
#[derive(Deserialize)]
// note, this causes deserialization to try the variants top-to-bottom
#[serde(untagged)]
enum FloatOrInt {
Int(i64),
Float(f64),
}
With the enum, the type parameter T on AllData is not needed anymore, you can go for:
#[derive(Deserialize)]
#[serde(transparent)]
struct AllData(Vec<Vec<FloatOrInt>>);
If you are sure that the inner array has always length 6, you can replace it with an array: [FloatOrInt; 6].
Putting it together, you'd get something like this:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=52bd3ce7779cc9b7b152c681de8681c4

Can I create string enum?

I am wondering is it possible to create enum with constant string values in Rust?
I found this previous question: How do I get an enum as a string? which shows a work around that I can use to stringify variants of an enum (I can use .to_string() on enum variants and get their name as a string).
That question was helpful, but this is what I want to achieve:
enum StringEnum {
Hello = "Hello",
World = "World"
}
If you have an enum like this:
enum HelloWorld {
Hello,
World
}
Hello and World are enum variants. Every variant of an enum is assigned to a single integer value. This is known as the discriminant. Currently, discriminants are only allowed to be integers, not arbitrary types like &'static str, although that may change in the future.
If you want to be able to convert your enum to a string, you can write a method that does this manually:
impl HelloWorld {
fn as_str(&self) -> &'static str {
match self {
HelloWorld::Hello => "Hello",
HelloWorld::World => "World"
}
}
}
Or you can have this done for you with the strum_macros crate:
#[derive(strum_macros::Display)]
pub enum StringEnum {
Hello,
World
}
fn main() {
let hello: &'static str = StringEnum::Hello.into(); // "Hello"
let world: String = StringEnum::World.to_string(); // "World"
}
There are a couple other methods mentioned in How do I get an enum as a string?
You can also have enum with data
pub enum TokenType<'a> {
STRING(&'a str),
}
and the this will give you the "test" value.
pub fn test() {
let token = TokenType::STRING("test");
if let TokenType::STRING(value) = token {
println!("{:?}", value)
}
}

How do I transform special values into Option::None when using Serde to deserialize?

I'm parsing data into:
struct Data {
field1: Option<f32>,
field2: Option<u64>,
// more ...
}
The problem is that my input data format formats what would be a None in Rust as "n/a".
How do tell Serde that an Option<T> should be None for the specific string n/a, as opposed to an error? We can assume that this doesn't apply to a String.
This isn't the same question as How to deserialize "NaN" as `nan` with serde_json? because that's creating an f32 from a special value whereas my question is creating an Option<Anything> from a special value. It's also not How to transform fields during deserialization using Serde? as that still concerns a specific type.
You can write your own deserialization function that handles this case:
use serde::de::Deserializer;
use serde::Deserialize;
// custom deserializer function
fn deserialize_maybe_nan<'de, D, T: Deserialize<'de>>(
deserializer: D,
) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
{
// we define a local enum type inside of the function
// because it is untagged, serde will deserialize as the first variant
// that it can
#[derive(Deserialize)]
#[serde(untagged)]
enum MaybeNA<U> {
// if it can be parsed as Option<T>, it will be
Value(Option<U>),
// otherwise try parsing as a string
NAString(String),
}
// deserialize into local enum
let value: MaybeNA<T> = Deserialize::deserialize(deserializer)?;
match value {
// if parsed as T or None, return that
MaybeNA::Value(value) => Ok(value),
// otherwise, if value is string an "n/a", return None
// (and fail if it is any other string)
MaybeNA::NAString(string) => {
if string == "n/a" {
Ok(None)
} else {
Err(serde::de::Error::custom("Unexpected string"))
}
}
}
}
Then you can mark your fields with #[serde(default, deserialize_with = "deserialize_maybe_nan")] to use this function instead of the default function:
#[derive(Deserialize)]
struct Data {
#[serde(default, deserialize_with = "deserialize_maybe_nan")]
field1: Option<f32>,
#[serde(default, deserialize_with = "deserialize_maybe_nan")]
field2: Option<u64>,
// more ...
}
Working playground example
More information in the documentation:
deserialize_with serde attribute
Untagged enum representation

How to parse field to string with Serde?

I have a custom field in my JSON which is coming dynamic and needs to be parsed to struct which has a HashMap field like following:
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
use std::collections::HashMap;
#[derive(Serialize, Deserialize)]
struct MyStruct {
field1: String,
custom: HashMap<String, String>,
}
fn main() {
let json_string = r#"{"field1":"3","custom":{"custom1":"15000","custom2":"60"}}"#;
let my_struct = serde_json::from_str::<MyStruct>(json_string).unwrap();
println!("{}", serde_json::to_string(&my_struct).unwrap());
}
It works when my json string has string fields in custom field which can be easily parsed to string.
But the problem is my json string is:
let json_string_wrong = r#"{"field1":"3","custom":{"custom1":15000,"custom2":"60"}}"#; // Need to parse this
How to handle such castings in serde?
Serde provides serde_json::Value ( reference ) . It is an enum which contains data types like:
pub enum Value {
/// Represents a JSON null value.
Null,
/// Represents a JSON boolean.
Bool(bool),
/// Represents a JSON number, whether integer or floating point.
Number(Number),
/// Represents a JSON string.
String(String),
/// Represents a JSON array.
Array(Vec<Value>),
/// Represents a JSON object.
Object(Map<String, Value>),
}
You can use serde_json::Value as a value type for your HashMap. It is simply possible to pull data from serde_json::Value with using serde_json::from_value or use pattern matching. In your case i would use pattern matching, because only Integer types will be converted into a String and rest will be the same.
But you'll need to consider adding one more step after deserialize. Like
Creating shadow field for custom, will be filled after deserialization.
Or constructing new struct which contains custom as HashMap<String, String>.
Add a function to convert HashMap<String, Value> to HashMap<String, String>,
Implementation of this trait can solve your problem.
trait ToStringStringMap {
fn to_string_string_map(&self) -> HashMap<String, String>;
}
impl ToStringStringMap for HashMap<String, Value> {
fn to_string_string_map(&self) -> HashMap<String, String> {
self.iter()
.map(|(k, v)| {
let v = match v.clone() {
e # Value::Number(_) | e # Value::Bool(_) => e.to_string(),
Value::String(s) => s,
_ => {
println!(r#"Warning : Can not convert field : "{}'s value to String, It will be empty string."#, k);
"".to_string()
}
};
(k.clone(), v)
})
.collect()
}
}
Example: Playground
Note: Trait's name is not well chosen, suggestions are welcomed.

How do I create a Rust HashMap where the value can be one of multiple types?

I want to make a JSON object which includes multiple types. Here's the structure:
{
"key1": "value",
"key2": ["val", "val", "val"]
"key3": { "keyX": 12 }
}
How can I make a HashMap which accepts all these types?
I'm trying this:
let item = HashMap::new();
item.insert("key1", someString); //type is &str
item.insert("key2", someVecOfStrings); //type is Vec<String>
item.insert("key3", someOtherHashMap); //Type is HashMap<&str, u32>
let response = json::encode(&item).unwrap();
I know that the hash map does not have enough type info, but I'm not sure how I can make it work. I have tried setting an explicit type on item which was HashMap<&str, Encodable> but then it's just another error. What is the correct way to do this?
You should use an enum type as value in your HashMap. That enum needs to have a variant for each possible type (boolean, number, string, list, map...) and an associated value of appropriate type for each variant:
enum JsonValue<'a> {
String(&'a str),
VecOfString(Vec<String>),
AnotherHashMap(HashMap<&'a str, u32>),
}
Fortunately, there already is an implementation of a JSON value type, part of the serde_json crate which is built on the serde crate.
Here is how your code would look if you used the serde_json crate:
extern crate serde_json;
use serde_json::{Value, Map, Number};
fn main() {
let mut inner_map = Map::new();
inner_map.insert("x".to_string(), Value::Number(Number::from(10u64)));
inner_map.insert("y".to_string(), Value::Number(Number::from(20u64)));
let mut map = Map::new();
map.insert("key1".to_string(), Value::String("test".to_string()));
map.insert(
"key2".to_string(),
Value::Array(vec![
Value::String("a".to_string()),
Value::String("b".to_string()),
]),
);
map.insert("key3".to_string(), Value::Object(inner_map));
println!("{}", serde_json::to_string(&map).unwrap());
// => {"key1":"test","key2":["a","b"],"key3":{"x":10,"y":20}}
}
Here is another approach that may be more palatable to you. The serde_json crate provides a way to construct serde_json::Value objects from JSON literals. Your example would look like this:
use serde_json::json;
fn main() {
let item = json!({
"key1": "value",
"key2": ["val", "val", "val"],
"key3": { "keyX": 12 }
});
let response = serde_json::to_string(&item).unwrap();
}

Resources