Rust/Serde: serialize external struct to json camelcase - json

I am working on some code that takes a struct returned by an external library, serializes it to json, and serializes the json to protobuf using pbjson. The external library uses serde and implements Serialize, but the json that is returned is snake case. The problem is that pbjson is expecting the json to be camelcase.
How can I get a camelcase version of the serde json object? (ie configure the external library to use something like #[serde(rename_all = "camelCase")] or to convert the json keys to camelcase?)
Note: I am working with many remote structs that in total add up to almost 2k lines of code. I would like to avoid recreating these types locally if possible.

If I understand correctly, you want something like this? Basically you just need to turn the foreign items into items of your own type and then serialize those.
// foreign struct
#[derive(Serialize, Deserialize)]
struct Foreign {
the_first_field: u32,
the_second_field: String,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Mine {
the_first_field: u32,
the_second_field: String,
}
impl From<Foreign> for Mine {
fn from(
Foreign {
the_first_field,
the_second_field,
}: Foreign,
) -> Self {
Self {
the_first_field,
the_second_field,
}
}
}
fn main() {
// only used to construct the foreign items
let in_json = r#"[{"the_first_field": 1, "the_second_field": "second"}]"#;
let foreign_items = serde_json::from_str::<Vec<Foreign>>(in_json).unwrap();
let mine_items = foreign_items.into_iter().map(Mine::from).collect::<Vec<_>>();
let out_json = serde_json::to_string(&mine_items).unwrap();
println!("{}", out_json); // [{"theFirstField":1,"theSecondField":"second"}]
}

Related

Transforming "null" in JSON to empty String instead of "None"

I'm using serde to deserialize a JSON file and one of it's values is a String.
To read it, I'm using:
#[serde(default)]
pub key: Option<String>,
because in the JSON file I can have null (then Option handles) or not even pass it (#serde(default)] handles it).
The problem I'm having is that when in the null case, the Option is returning None, which is giving me trouble later. I have to later match the Strings and convert to an i8 like this:
let mut transformed: i8 = 0;
if key.as_ref().unwrap() == "H" {
transformed = 1;
}
else {
transformed = -1; // Case that I'm looking for when null in JSON
}
I searched for match practices to handle the None, but it's also giving me trouble with the String vs &str problem, so I'm looking for a way of when deserializing, assign an empty String "" instead of None, so later I can compare in the same way I'm already doing.
Also would appreciate less verbose solution to directly parse and assign an i8.
As mentioned by mcarton in the comments, the easiest solution is to stick with using an Option<String> in you struct and use .unwrap_or_default() in the code consuming the data. If this isn't an option for you, you can provide a custom deserializer using the #[serde(deserialize_with=...)] attribute:
use serde_json::from_str;
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct A {
#[serde(deserialize_with = "null_to_default")]
key: String,
}
fn null_to_default<'de, D, T>(de: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Default + Deserialize<'de>,
{
let key = Option::<T>::deserialize(de)?;
Ok(key.unwrap_or_default())
}
fn main() {
let a: A = from_str(r#"{"key": null}"#).unwrap();
dbg!(a);
}
(Playground)

Deserialize json based on an enum in the json

Is it possible to use a value in JSON to determine how to deserialize the rest of the JSON using serde? For example, consider the following code:
use serde::{Serialize, Deserialize};
use serde_repr::*;
#[derive(Serialize_repr, Deserialize_repr, Debug)]
#[repr(u8)]
enum StructType {
Foo = 1,
Bar = 2
}
#[derive(Serialize, Deserialize, Debug)]
struct Foo {
a: String,
b: u8
}
#[derive(Serialize, Deserialize, Debug)]
struct Bar {
x: String,
y: u32,
z: u16
}
#[derive(Serialize, Deserialize, Debug)]
struct AllMyStuff {
type: StructType,
data: //HELP: Not sure what to put here
}
What I'm trying to achieve is deserialization of the data, even if in multiple steps, where the type field in the AllMyStuff determines which type of struct data is present in data. For example, given the following pseudocode, I'd like to ultimately have a Bar struct with the proper data in it:
data = {"type": "2", "data": { "x": "Hello world", "y": "18", "z": "5" } }
// 1) use serde_json to deserialize a AllMyStuff struct, not erroring on the "data" blob
// 2) Now that we know data is of type "2" (or Bar), parse the remaining "data" into a AllMyStuff struct
If steps (1) and (2) are somehow able to be done in a single step, that would be awesome but not needed. I'm not sure what type of type to declare data in the AllMyStuff struct to enable this as well.
You can use serde_json::Value as the type for AllMyStuff::data. It will deserialize any valid json object and also implements Deserialize itself, so it can be further deserialized once the type to deserialize to is known (via AllMyStuff::type). While this requires more intermittent steps and (mostly temporary) types, it saves you from manually implementing Deserialize on an enum AllMyStuff { Foo(Foo), Bar(Bar) }.
I may be missing something, but AllMyStuff looks as if you are trying to manually distinguish between Foo and Bar.
However, Rust, has a built-in way of doing this:
#[derive(Serialize, Deserialize, Debug)]
enum AllMyStuff {
Foo(Foo),
Bar(Bar),
}
Click here to see it in action.

Deserialize JSON when structure known but not the keys

I am trying to deserialize some crypto exchange JSON. The structure of the JSON is predictable but the keys are not. Each time the server adds a new currency it becomes a new key. For example, it looks something like this:
{
"timestamp":"1562405",
"username":"1234",
"BTC":
{"available":"0.00","orders":"0.00000000"},
"BCH":
{"available":"0.0000000","orders":"0.00000000"},
..
..
"GUSD":
{"available":"0.00","orders":"0.00"}
}
I tried the usual approach of defining a struct to deserialize into but every time there is a new currency my program will have an error.
I thought I would just read it all into a Value and then just iterate over it manually to put it all into a struct.
let balance_data: Value = serde_json::from_str(&String::from_utf8_lossy(&body)).unwrap();
println!("balance_data: {:?}", balance__data);
for element in balance_data.iter() {
//push into a proper map...
}
But I can't:
no method named `iter` found for type `serde_json::Value`.
I thought I would put it into a Vec<Value> but this causes a panic:
thread 'main' panicked at 'called ``Result::unwrap()` on an `Err` value: Error("invalid type: map, expected a sequence", line: 1, column: 0)'`
I also experimented with reading it into a HashMap but couldn't quite seem to crack it.
It would be amazing if we could do something like:
let balance_data: RecvBalance = serde_json::from_str(&String::from_utf8_lossy(&body)).unwrap();
#[derive(Debug, Deserialize)]
struct RecvBalance {
timestamp: String,
username: String,
HashMap<String, RecvBalanceData>,
}
#[derive(Debug, Deserialize)]
struct RecvBalanceData {
available: String,
orders: String,
}
Has anyone dealt with this situation? I need a struct with the balance data in it that I can lookup later in my program.
The doc has a related example. You need to put the HashMap and use flatten attribute:
#[derive(Debug, Deserialize)]
struct RecvBalance {
timestamp: String,
username: String,
#[serde(flatten)]
moneys: HashMap<String, RecvBalanceData>,
}

How to (de)serialize a strongly typed JSON dictionary in Serde?

I am writing a Rust application that handles JSON messages from a TypeScript client with a public interface. I have written some code using serde_derive and it works well, but I can't figure out how to implement dictionaries; e.g.:
{
"foo" : { "data" : 42 },
"bar" : { "data" : 1337 }
}
Here the keys are the strings "foo" and "bar" and the dictionary's values follow this schema:
use serde_derive;
use serde_json::Number;
#[derive(Serialize, Deserialize)]
struct DictionaryValue {
data: Number,
}
I am looking to access the JSON data in this manner:
#[derive(Serialize, Deserialize)]
struct Dictionary {
key: String,
value: DictionaryValue,
}
How can I (de)serialize my JSON data into/from Dictionary using Serde?
You have a logic error in your code. The structure in your JSON file describes an associative array but your Dictionary does not support multiple key-value-pairs. As Stargateur stated in the comments, you may use HashMap as Serde has Serialize and Deserialize implementations for HashMap.
Instead of using a single key-value-pair, you can rewrite your Dictionary as
type Dictionary = HashMap<String, DictionaryValue>;
and you can retrieve the data for example by
let dict: Dictionary = serde_json::from_str(json_string).unwrap();
If you now want to wrap everything in a Dictionary-struct it will look like this:
#[derive(Serialize, Deserialize)]
struct Dictionary {
inner: HashMap<String, DictionaryValue>,
}
The problem is, that serde_json now expects
{
"inner": {
"foo" : { "data" : 42 },
"bar" : { "data" : 1337 }
}
}
To get rid of this, you can add the serde(flatten) attribute to Dictionary:
#[derive(Serialize, Deserialize, Debug)]
struct Dictionary {
#[serde(flatten)]
inner: HashMap<String, DictionaryValue>,
}
If HashMap or any BTreeMap from std does not fit your needs, you can also implement your Dictionary on your own. See the docs here and here for more details.

Rust and JSON serialization

If the JSON object is missing some fields, the decode function throws an exception. For example:
extern crate rustc_serialize;
use rustc_serialize::json;
use rustc_serialize::json::Json;
#[derive(RustcDecodable, RustcEncodable, Debug)]
enum MessageType {
PING,
PONG,
OPT,
}
#[derive(RustcDecodable, RustcEncodable, Debug)]
pub struct JMessage {
msg_type: String,
mtype: MessageType,
}
fn main() {
let result3 = json::decode::<JMessage>(r#"{"msg_type":"TEST"}"#);
println!("{:?}", result3);
// this will print `Err(MissingFieldError("mtype"))`
let result = json::decode::<JMessage>(r#"{"msg_type":"TEST", "mtype":"PING"}"#);
println!("{:?}", &result);
// This will print Ok(JMessage { msg_type: "TEST", mtype: PING })
let result2 = Json::from_str(r#"{"msg_type":"TEST", "mtype":"PING"}"#).unwrap();
println!("{:?}", &result2);
// this will print Object({"msg_type": String("TEST"), "mtype": String("PING")})
}
Is there a way to specify that some fields in a struct are optional?
Why does the function from_str not serialize mtype as an enum?
No, there is no such way. For that, you need to use serde. Serde also has lots of other features, but unfortunately it is not as easy to use as rustc_serialize on stable Rust.
Well, how should it? Json::from_str returns a JSON AST, which consists of maps, arrays, strings, numbers and other JSON types. It simply cannot contain values of your enum. And also there is no way to indicate that you want some other type instead of string, naturally.
Regarding the first question, you can use Option. For example:
pub struct JMessage {
msg_type: Option<String>,
mtype: MessageType,
}
Which defaults to None if the field does not exist.