Как десериализовать JSON в Rust при объединении плоского компонента с необходимостью конвертировать ключи?

Прошу прощения за несколько неясный вопрос, но я не могу сформулировать его одним предложением. Ниже приведен игрушечный пример, иллюстрирующий проблему:

use serde_json;
use serde_json::Value;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Deserialize, Serialize)]
struct RawInfo {
    info: HashMap<u32,String>
}

#[derive(Deserialize, Serialize)]
struct InfoContainer {
    info: HashMap<u32,String>
}

#[derive(Deserialize, Serialize)]
struct ContainedInfo {
    #[serde(flatten)]
    container: InfoContainer
}

#[derive(Deserialize, Serialize)]
struct InfoContainer2 {
    info: HashMap<String,String>
}

#[derive(Deserialize, Serialize)]
struct ContainedInfo2 {
    #[serde(flatten)]
    container: InfoContainer2
}

fn main() {
    let jstr = r#"
{
    "info": {
        "1": "first",
        "2": "second",
        "3": "third"
    }
}
    "#;
    
    let jv: Value = match serde_json::from_str(jstr) {
        Ok(jv) => jv,
        Err(jv) => {
            println!("serde json error: {}", jv);
            panic!();
        }
    };
    
    
    let raw_deserialized = match <RawInfo as Deserialize>::deserialize(jv.clone()) {
        Ok(raw_deserialized) => println!("raw is ok"),
        Err(raw_deserialized) => println!("raw is error: {}", raw_deserialized)
    };
    
    let container_deserialized = match <ContainedInfo as Deserialize>::deserialize(jv.clone()) {
        Ok(container_deserialized) => println!("container is ok"),
        Err(container_deserialized) => println!("container is error: {}", container_deserialized)
    };

    let container_deserialized2 = match <ContainedInfo2 as Deserialize>::deserialize(jv.clone()) {
        Ok(container_deserialized2) => println!("container2 is ok"),
        Err(container_deserialized2) => println!("container2 is error: {}", container_deserialized2)
    };
    
}

Это приводит к следующему результату:

raw is ok
container is error: invalid type: string "1", expected u32
container2 is ok

Таким образом, когда структура Rust имеет ту же структуру, что и JSON, преобразование десериализации ключей работает нормально, но мы пытаемся перенастроить его с помощью flatten, но это не удается, поскольку целевой HashMap имеет целочисленные ключи, а исходный — строковые.

Ключи JSON должны быть строками, поэтому я не могу это изменить, и мне нужны целочисленные ключи в моем магазине HashMap. Однако мне бы хотелось еще и сглаживания. Могу ли я добиться этого без необходимости писать собственный десериализатор?

Отвечает ли это на ваш вопрос? serde_json выравнивает объект с индексами в качестве ключей

Chayim Friedman 24.07.2024 20:19
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
0
1
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это очень интересно. У serde_json есть хак для поддержки десериализации карт с целочисленными ключами, но этот хак не работает с #[serde(flatten)], потому что тип не говорит, что ожидает карту.

К счастью, как и во многих других случаях с serde, вам на помощь приходит serde_with:

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

#[serde_with::serde_as]
#[derive(Debug, Deserialize, Serialize)]
struct InfoContainer {
    #[serde_as(as = "HashMap<serde_with::DisplayFromStr, _>")]
    info: HashMap<u32, String>,
}

#[derive(Debug, Deserialize, Serialize)]
struct ContainedInfo {
    #[serde(flatten)]
    container: InfoContainer,
}

fn main() {
    let jstr = r#"
{
    "info": {
        "1": "first",
        "2": "second",
        "3": "third"
    }
}
    "#;

    let container_deserialized = serde_json::from_str::<ContainedInfo>(jstr).unwrap();
    dbg!(container_deserialized);
}

Другие вопросы по теме