Как десериализовать BSON в общий объект? - PullRequest
0 голосов
/ 04 января 2019

Я использую Serde для десериализации объектов BSON в экземплярах структуры Rust. Я могу десериализовать объекты в конкретные экземпляры структуры, но как я могу десериализовать в общем?

У меня есть коллекции "стран" и "городов" в MongoDB. В программе Rust у меня есть структура для Country и City. Когда я вытаскиваю страну или город из Монго, я могу десериализовать их, используя Serde, в структуру Country или City. Смотрите вторую строку в main() ниже.

Я хочу десериализовать объект BSON в общий Location объект. Основываясь на том, что я читал об обобщениях в книге Rust, я создал черту LocationTrait и реализовал ее для Country и City. (см. строку 3 в main()). Не компилируется, говоря размер значений типа dyn LocationTrait не может быть известен во время компиляции.

#[derive(Serialize, Deserialize)]
pub struct Country {
    pub name: String,
}

#[derive(Serialize, Deserialize)]
pub struct City {
    pub name: String,
}

pub trait LocationTrait {}
impl LocationTrait for Country {}
impl LocationTrait for City {}

fn main() {
    let item = mongo_coll
        .find_one(Some(doc! {"name": "usa"}), None)
        .unwrap()
        .unwrap();
    let country: Country = bson::from_bson(bson::Bson::Document(item)).unwrap();
    // fails -> let gen_location: LocationTrait = bson::from_bson(bson::Bson::Document(item)).unwrap();
}

В конце концов, я хотел бы создать общий объект, который представляет Country или City. Но я не уверен в отправной точке - мне нужно сосредоточиться на черте или мне нужно создать новую структуру, связанную с чертами?

1 Ответ

0 голосов
/ 05 января 2019

Есть две проблемы, препятствующие компиляции вашего кода.

Первая ошибка, которую вы увидели: the size for values of type dyn LocationTrait cannot be known at compilation time, связана с тем, что bson::from_bson необходимо вернуть результат десериализации по значению. Компилятору нужно знать, сколько места ему нужно выделить в стеке вызовов, чтобы вернуть его.

Однако признаки являются абстракцией для описания поведения, а не данных, поэтому его можно реализовать для u8 (одного байта) или гораздо большей структуры.

Чтобы иметь возможность вернуть такое значение, вам нужно его поставить в рамку (см. Объекты черты ).

Вторая проблема заключается в том, что возвращаемое значение должно реализовывать черту Deserialize (а не LocationTrait)

Чтобы решить эту проблему:

Самый простой путь - использовать перечисления вместо черт:

#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Location {
    Country(Country),
    City(City)
}

Это будет работать с такими документами, как {"type" = "Country", name="usa"}. Проверьте Serde doc для дополнительных опций.

Если вы действительно хотите использовать черты (например, чтобы иметь возможность определять типы вне этого модуля), вам понадобится черта в штучной упаковке и пользовательские структуры как таковые:

// The same trait as defined earlier
pub trait LocationTrait {}
impl LocationTrait for Country {}
impl LocationTrait for City {}

// A custom struct on which you can implement the deserialize trait
// Needed as both Deserialize and Box are defined outside this crate.
struct DynLocation(Box<dyn LocationTrait>);

impl<'de> Deserialize<'de> for DynLocation {
    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        // Tricky part ommited here:
        // You will need to partially deserialize you object
        // in order to get a first discriminant before instanciating
        // and deserializing the proper type.
        unimplemented!()
    }
}

// The public method to hide the DynLocation wrapper
pub fn deserialize(item: &str) -> Box<dyn LocationTrait> {
    let location: DynLocation = serde_json::from_str(item).expect("invalid json");
    location.0
}

Некоторое обсуждение по этой же теме можно найти в Как можно добавить десериализацию объектов полиморфных признаков в Rust, если вообще? .

...