Recently I ran into a bug in my code; hey, it happens. The bug was that I had a struct which could serialize into json, but could not deserialize from its own json. The struct holds a value for a mac address, which is 48-bit integer (that i store in a u64), but it is serialized using the network interface name. For example on my mac, i have a network interface named en1 with the mac address of 20:c9:d0:b0:a4:71:

(Semi-spoiler,dtolnay showed this method as a comment to my previous reddit post. I already had this written, so i decided to post and give some explanation on it anyway).

%› ifconfig en1 ether
ether 20:c9:d0:b0:a4:71


So, the JSON { "mac_addr": "en1" } will deserialized into the struct MacAddrExample { mac_addr: 36051161752689 }. The reason behind this kind of deserialization is that I use this to deserialize a config file, and i don’t want MAC address in the config file, i only want network interface names.

The simplified struct is basic,

 #[derive(Debug, Serialize, Deserialize)]
#[serde(deserialize_with = "mac_to_u64")]
}


So, if I deserialze { "mac_addr": "en1" } to get the sturct MacAddrExample { mac_addr: 36051161752689 }, then serialize that struct i’ll get { "mac_addr": 36051161752689 }, then deserialize that JSON, and boom, panic!

Error("invalid type: integer 36051161752689, expected a string"


That’s a bust.

I want to be able to deserialize both … which sounds familiar. However in my previous example, i was deserializing an enum, whereas this time i’m deserializing a primitive. If you try to implement Deserialize for a primitive, you’ll get an error since serde already defines that trait for u64:

error[E0119]: conflicting implementations of trait serde::Deserialize<'_> for type u64:
--> src/main.rs:74:1
|
74 | impl<'de> Deserialize<'de> for u64 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate serde:
- impl<'de> serde::Deserialize<'de> for u64;


And, you get an error because of Rust’s orphan rules for trait implementations.

error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
--> src/main.rs:74:1
|
74 | impl<'de> Deserialize<'de> for u64 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl doesn't use types inside crate
|
= note: the impl does not reference any types defined in this crate
= note: define and implement a trait or new type instead


Double whammy!

We need to find another way to get this job done. If you recall the definition of the the struct above, the struct uses the attribute deserialize_with which tells serde to use a function to deserialize the data, in this case, the function has the function signature of:

fn mac_to_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where D: Deserializer<'de>


This must take an object that is generic over Deserializer as input. But now to we make that deserialize a string an u64? It is possible to do, but we need to introduce a new enum which contains the types we want to accept as input (i.e. String and u64) and deserialize that enum using a function.

#[derive(Deserialize)]
#[serde(untagged)]
enum MacOrU64 { U64(u64), Mac(String) }
pub fn mac_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where D: Deserializer<'de>
{
match MacOrU64::deserialize(deserializer)? {
MacOrU64::U64(v) => { Ok(v) }
MacOrU64::Mac(v) => {
}
}
}


Note the #[serde(untagged)] attribute which makes the serialized JSON an anonymous object. See the serde documentation for serge’s enum representations.

The body of the function deserialize the enum U64OrMac and you just need to match on the result.

Now we just need to change the attribute of the oringal struct to use the new function:

#[derive(Debug, Serialize, Deserialize)]