Я пытаюсь десериализовать следующие фрагменты 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, как указано выше?
Моя опечатка. Так и должно быть 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"
в строке «круг».
Предполагая, что у вас есть контроль над форматом 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?
Как мне определить структуру 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.
Ваши данные JSON недействительны JSON. Вы не можете иметь анонимный массив внутри объекта. Кроме того, можно ли изменить формат JSON или его необходимо десериализовать в текущем виде?