I am receiving a string like below (Not in JSON or HashMap neither) as kind of key value pair from implicit JSONWebkey crate:
{ "kid":"kid-value",
"kty":"RSA",
"use":"sig",
"n":"n-value",
"e":"e-value" }
Now how can I convert to proper HashMap to extract key and value of "e" and "n"? Or is there a simpler way to extract exact value of "e" and "n"?
The string is JSON, so you should just parse it. By default serde_json ignores all unknown fields, so declaring a struct with only the needed fields is enough:
#[derive(serde::Deserialize)]
struct Values {
n: String,
e: String,
}
fn main() -> Result<()> {
let s = r#"{ "kid":"kid-value",
"kty":"RSA",
"use":"sig",
"n":"n-value",
"e":"e-value" }"#;
let value = serde_json::from_str::<Values>(s)?;
println!("{}", value.e);
println!("{}", value.n);
Ok(())
}
Related
I am deserializing some JSON using Serde. I am having problems with a value that is usually an array of strings, but can also be the constant string "all". Expressed in JSON-schema it looks like this:
{
"oneOf": [
{
"const": "all"
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
}
I want to serialize it into this enum:
enum MyList {
List(Vec<String>),
All
}
Here are a few examples:
["foo", "bar"] // Should be MyList::List
"all" // Should be MyList::All
"foo" // Should be an error!
The question is, how do I deserialize this enum using serde? None of the container attributes or variant attributes seem to help. If need be, I may change the design of the enum. However, the structure of the JSON is beyond my control.
It's possible with the combination of an untagged enum representation and deserialize_with variant attribute:
use serde::{Deserialize, Deserializer};
#[derive(Debug, Deserialize)]
// This annotation "flattens" the enum, allowing one to treat a list of strings
// directly as a List variant, without extra annotations.
// Serde will attempt to deserialize input as each variant in order,
// returning an error if no one matches.
#[serde(untagged)]
enum MyList {
// This annotation delegates deserialization of this variant to own code,
// since otherwise Serde wouldn't know what to do with the string.
#[serde(deserialize_with = "all")]
All,
// This variant is deserialized as usual.
List(Vec<String>),
}
// A custom deserialization function, referred to by `deserialize_with`.
// (reference: https://github.com/serde-rs/serde/issues/1158)
fn all<'de, D>(deserializer: D) -> Result<(), D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
// This enum is, by default, "externally tagged";
// but, since it consists only of a single unit variant,
// it means that it can be deserialized only from
// the corresponding constant string - and that's exactly what we need
enum Helper {
#[serde(rename = "all")]
Variant,
}
// We're not interested in the deserialized value (we know what it is),
// so we can simply map it to (), as required by signature
Helper::deserialize(deserializer).map(|_| ())
}
fn main() {
// Trying to parse an array with both variants...
let json = r#"["all", ["a", "b", "c"]]"#;
let lists: Vec<MyList> = serde_json::from_str(json).expect("cannot parse");
// ...we can see that it is indeed parsed correctly...
println!("{:?}", lists);
// ...and any unexpected string results in an error
serde_json::from_str::<MyList>(r#""foo""#).unwrap_err();
}
Playground
I am trying to deserialize a JSON structure which has an array of strings, which are basically aliases to the enum I want to retrieve.
The JSON structure is:
{
"some_field": "abc",
"species": ["homosapiens", "hs", "human"]
}
And my target structure is:
#[derive(Deserialize, Debug)]
enum Species {
#[serde(alias = "homosapiens", alias = "hs")]
HomoSapiens,
#[serde(alias = "musmusculus", alias = "mouse")]
MusMusculus
}
#[derive(Deserialize, Debug)]
struct MyStruct {
species: Species,
}
My goal is, that in deserializing, it sees that the "species" field in the JSON contains at least one matching alias. So for example, if the JSON data has "hs" in the "species" array, it is seen as the enum variant "HomoSapiens", and if it were "mouse" it would be the enum variant "MusMusculus".
I tried to add a custom deserialization method but I can not find out how to check if one of the string values matches any of the aliases.
fn deserialize_species<'de, D>(deserializer: D) -> Result<Species, D::Error>
where
D: Deserializer<'de>,
{
let species_names: Vec<String> = Deserialize::deserialize(deserializer)?;
// TODO: Check if any of these strings are an alias
Err(D::Error::custom("Could not deserialize species"))
}
Note: I did not forget to add the #[serde(deserialize_with)] tag.
Is there a method on the "Species" enum I can call that tries to serialize it from one of these strings which keeps the aliases in mind? I can not find it in the documentation but I might have looked over it.
This deserializes every string in the vector and returns the first species, which can be deserialized.
fn deserialize_species<'de, D>(deserializer: D) -> Result<Species, D::Error>
where
D: Deserializer<'de>,
{
let species_names: Vec<String> = Deserialize::deserialize(deserializer)?;
for sn in species_names {
if let Ok(species) = serde_plain::from_str(&sn) {
return Ok(species);
}
}
Err(D::Error::custom("Could not deserialize species"))
}
Is there a method on the "Species" enum I can call that tries to serialize it from one of these strings which keeps the aliases in mind?
The method is the deserialize method. serde does not offer any way to inspect the generated code. But you can use a simple deserializer like serde_plain to use it.
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
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.
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();
}