There's a well-known (and frequently encouraged) workaround for the orphan rule: Create a wrapper type.
Let's say you have one library with:
pub struct TypeWithSomeSerialization { /* public fields here */ }
And you want to define a custom serialization. In this case, you can write: pub struct TypeWithDifferentSerialization(TypeWithSomeSerialization)
Then you just implement Serialize and Deserialize for TypeWithDifferentSerialization.This cover most occasional cases where you need to work around the orphan rule. And semantically, it's pretty reasonable: If a type behaves differently, then it really isn't the same type.
The alternative is to have a situation where you have library A define a data type, library B define an interface, and library C implement the interface from B for the type from A. Very few languages actually allow this, because you run into the problem where library D tries to do the same thing library C did, but does it differently. There are workarounds, but they add complexity and confusion, which may not be worth it.
The gotcha is what happens when TypeWithSomeSerialization is not something you’re using directly but is contained within SomeOtherTypeWithSomeSerialization which you are using directly. Then things get messy.