Как десериализовать массив JSON в структуру с помощью serde?

Я пытаюсь десериализовать следующие фрагменты JSON в Vec структуры Shape:

use serde::{Deserialize, Serialize};
use serde_json::{Result, Value};

#[derive(Debug, Serialize, Deserialize)]
struct Shape {  // this struct is not working, for display purpose only
    shape_type: String,
    d0: f64,
    d1: f64,
    d2: f64, //optional, like the case of "dot"
    d3: f64, //optional, like the case of "circle"
}

let json = r#"
  {[
    ["line", 1.0, 1.0, 2.0, 2.0],
    ["circle", 3.0, 3.0, 1.0],
    ["dot", 4.0, 4.0]
  ]}"#;

let data: Vec<Shape> = match serde_json::from_str(json)?;

Очевидно, что для описания каждого типа Shape требуется String и разное количество f64. Как мне определить структуру Shape для десериализации данных JSON, как указано выше?

Ваши данные JSON недействительны JSON. Вы не можете иметь анонимный массив внутри объекта. Кроме того, можно ли изменить формат JSON или его необходимо десериализовать в текущем виде?

pretzelhammer 25.12.2020 17:45

Моя опечатка. Так и должно быть let json = r#"{"shapes": [["line", 1.0, 1.0, 2.0, 2.0], ..."#; К сожалению, я не могу изменить формат JSON. Я попытался изменить тип d2 и d3 на Option<f64>, но безуспешно: "invalid length 4, expected struct Shape with 5 elements" в строке «круг».

Cuteufo 25.12.2020 18:04
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
2
2
7 036
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

Предполагая, что у вас есть контроль над форматом JSON, я настоятельно рекомендую преобразовать тип Shape в enum, который может представлять несколько форм, и использовать макросы serde для автоматической реализации Serialize и Deserialize для Shape. Пример:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Point {
    x: f64,
    y: f64,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Shape {
    Dot { position: Point },
    Line { start: Point, end: Point },
    Circle { center: Point, radius: f64 },
}

fn main() {
    let shapes = vec![
        Shape::Dot {
            position: Point { x: 3.0, y: 4.0 },
        },
        Shape::Line {
            start: Point { x: -2.0, y: 1.0 },
            end: Point { x: 5.0, y: -3.0 },
        },
        Shape::Circle {
            center: Point { x: 0.0, y: 0.0 },
            radius: 7.0,
        },
    ];

    let serialized = serde_json::to_string(&shapes).unwrap();
    println!("serialized = {}", serialized);

    let deserialized: Vec<Shape> = serde_json::from_str(&serialized).unwrap();
    println!("deserialized = {:?}", deserialized);
}

детская площадка

Если вы абсолютно не можете изменить формат JSON, то serde вам не поможет. Сериализация фигуры как разнородного массива строк и чисел с плавающей запятой — очень странный выбор. Вы должны вручную разобрать его (или, по крайней мере, использовать какой-нибудь парсер, чтобы помочь вам), а затем вручную реализовать трейт Deserializer, чтобы он превратил его в Shape.

Хороший пост. Я выясню, можно ли изменить формат JSON. Просто любопытно, этот фрагмент JSON можно очень легко разобрать с помощью Python json.loads(text). Список Python может содержать разные типы данных и переменное количество элементов. Есть ли в rust тип данных, похожий на список Python?

Cuteufo 26.12.2020 02:41

Как мне определить структуру Shape для десериализации данных JSON, как указано выше?

Вы бы этого не сделали, потому что схема сериализации, которую вы хотите, на самом деле не имеет смысла ржаветь, и AFAIK serde ее не поддерживает (даже если вы используете enum вариант кортежа, tag = "type" для них не поддерживается).

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

Тем более что арность меняется для каждого "типа", иначе бы работало https://crates.io/crates/serde_tuple (хотя всегда можно было посмотреть, работает ли skip_serializing_if с serde_tuple, что позволило бы подавить " дополнительные поля).

Звучит вполне выполнимо,

Смотрите https://serde.rs/enum-representations.html

Ваш код будет использовать представления enum без тегов и будет выглядеть так:

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
#[allow(non_camel_case_types)]
enum Shape {
    line(String, f64, f64, f64, f64),
    circle(String, f64, f64, f64),
    dot(String, f64, f64),
}

#[derive(Debug, Serialize, Deserialize)]
struct ShapeList {
    shapes: Vec<Shape>
}

fn main() {
    let json = r#"{"shapes": [
        ["line", 1.0, 1.0, 2.0, 2.0],
        ["circle", 3.0, 3.0, 1.0],
        ["dot", 4.0, 4.0],
        ["circle2", 8.0, 3.0, 16.0]
      ]}"#;
    
    let data: ShapeList = serde_json::from_str(json).unwrap();
    println!("{data:#?}");
}

Выходы:

ShapeList {
    shapes: [
        line(
            "line",
            1.0,
            1.0,
            2.0,
            2.0,
        ),
        circle(
            "circle",
            3.0,
            3.0,
            1.0,
        ),
        dot(
            "dot",
            4.0,
            4.0,
        ),
        circle(
            "circle2",
            8.0,
            3.0,
            16.0,
        ),
    ],
}

Я немного изменил ваши данные, чтобы подчеркнуть, что распознавание типа основано не на фактическом значении этого первого столбца, а на «подписи» массива.

В конечном итоге лучшим способом было бы написать собственную реализацию Serialize/Deserialize, которая должна быть достаточно хорошо задокументирована на веб-сайте serde.

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