Как сопоставить массив объектов JSON по свойству в структуру, содержащую векторы свойств каждого объекта?

Я получаю поток данных датчиков, которые мне нужно агрегировать и выполнять базовую статистику (среднее, максимальное, минимальное и т. д.). Существует несколько значений, но данные датчиков могут быть несовместимыми, а некоторые значения могут отсутствовать.

Из чтения книги кажется, что Option следует использовать, когда значения luminosity или color отсутствуют, но я смущен этим.

Это пример данных моего датчика:

[
    {
        "sensor": "left",
        "luminosity": "50",
        "color": "(255,0,0)"
    },
    {
        "sensor": "left",
        "color": "#0f0"
    },
    {
        "sensor": "right",
        "luminosity": "20"
    },
    {
        "sensor": "right",
        "luminosity": "40",
        "color": "(255,0,0)"
    },
    {
        "sensor": "left",
        "luminosity": "30"
    },
    {
        "sensor": "top",
        "luminosity": "10"
    },
    {
        "sensor": "right",
        "color": "(0,0,0)"
    }
]

Данные каждого датчика будут храниться в экземпляре следующей структуры:

struct Data {
    pub luminosity: Vec<String>,
    pub color: Vec<String>,
}

Я хочу перебрать вышеуказанный объект JSON, сопоставить датчик с правильным экземпляром структуры («правильные» датчики с «правильной» структурой датчика) и поместить содержимое каждого наблюдения JSON в векторы (внутри каждого экземпляра структуры).

Отсутствующие значения должны быть записаны, чтобы для каждого «наблюдения» было одно действие нажатия для каждого вектора в структуре для соответствующего экземпляра структуры датчика.


person Greg    schedule 12.11.2017    source источник
comment
Давайте продолжим обсуждение в чате.   -  person Greg    schedule 12.11.2017
comment
Это было отправлено на форум пользователя.   -  person Shepmaster    schedule 13.11.2017


Ответы (2)


Что-то вроде этого должно работать. Он использует Serde для чтения каждого элемента массива JSON во вспомогательную структуру с требуемым именем датчика String и Option<String> данными для каждого значение датчика. Затем он перебирает эти показания и вставляет их в карту, в которой ключом является имя датчика, а значением является вектор данных для каждого значения датчика.


#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use std::collections::BTreeMap as Map;
use std::error::Error;

#[derive(Debug, Default)]
struct Data {
    luminosity: Vec<Option<String>>,
    color: Vec<Option<String>>,
}

fn main() {
    let input = r##"[
                      {
                        "sensor": "left",
                        "luminosity": "50",
                        "color": "(255,0,0)"
                      },
                      {
                        "sensor": "left",
                        "color": "#0f0"
                      },
                      {
                        "sensor": "right",
                        "luminosity": "20"
                      },
                      {
                        "sensor": "right",
                        "luminosity": "40",
                        "color": "(255,0,0)"
                      },
                      {
                        "sensor": "left",
                        "luminosity": "30"
                      },
                      {
                        "sensor": "top",
                        "luminosity": "10"
                      },
                      {
                        "sensor": "right",
                        "color": "(0,0,0)"
                      }
                    ]"##;
    let m = read_sensor_data(input).unwrap();
    println!("{:#?}", m);
}

fn read_sensor_data(input: &str) -> Result<Map<String, Data>, Box<Error>> {
    // Private helper struct that matches the format of the raw JSON
    #[derive(Deserialize)]
    struct RawReading {
        sensor: String,
        luminosity: Option<String>,
        color: Option<String>,
    }

    // Deserialize the raw data
    let raw_readings: Vec<RawReading> = serde_json::from_str(input)?;

    // Loop over raw data and insert each reading into the right sensor's struct
    let mut m = Map::new();
    for raw in raw_readings {
        // Look up this sensor's Data struct
        let sensor = m.entry(raw.sensor).or_insert_with(Data::default);

        // One push for every vector in the struct, even for missing observations
        sensor.luminosity.push(raw.luminosity);
        sensor.color.push(raw.color);
    }
    Ok(m)
}
person dtolnay    schedule 12.11.2017

Вы можете быть немного эффективнее за счет большего количества кода. Десериализация Vec не требуется, если вы создаете собственную реализацию Visitor:

extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use std::collections::HashMap;
use std::fmt;
use serde::de::{Deserialize, Deserializer, Visitor};

#[derive(Debug, Default)]
struct Data {
    luminosity: Vec<Option<String>>,
    color: Vec<Option<String>>,
}

struct Wrapper(HashMap<String, Data>);

impl<'de> Deserialize<'de> for Wrapper {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_seq(WrapperVisitor)
    }
}

struct WrapperVisitor;

impl<'de> Visitor<'de> for WrapperVisitor {
    type Value = Wrapper;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a sequence of measurement objects")
    }

    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
    where
        A: serde::de::SeqAccess<'de>,
    {
        #[derive(Debug, Deserialize)]
        struct DataPoint {
            sensor: String,
            luminosity: Option<String>,
            color: Option<String>,
        }

        let mut all_data = HashMap::new();

        while let Some(data_point) = seq.next_element::<DataPoint>()? {
            let data = all_data
                .entry(data_point.sensor)
                .or_insert_with(Data::default);
            data.luminosity.push(data_point.luminosity);
            data.color.push(data_point.color);
        }

        Ok(Wrapper(all_data))
    }
}

fn main() {
    let input = r###"
[
    {
        "sensor": "left",
        "luminosity": "50",
        "color": "(255,0,0)"
    },
    {
        "sensor": "left",
        "color": "#0f0"
    },
    {
        "sensor": "right",
        "luminosity": "20"
    },
    {
        "sensor": "right",
        "luminosity": "40",
        "color": "(255,0,0)"
    },
    {
        "sensor": "left",
        "luminosity": "30"
    },
    {
        "sensor": "top",
        "luminosity": "10"
    },
    {
        "sensor": "right",
        "color": "(0,0,0)"
    }
]
"###;

    let data = serde_json::from_str::<Wrapper>(input).expect("Nope");
    let data = data.0;

    println!("{:#?}", data);
}

Это производит вывод:

{
    "left": Data {
        luminosity: [
            Some("50"),
            None,
            Some("30")
        ],
        color: [
            Some("(255,0,0)"),
            Some("#0f0"),
            None
        ]
    },
    "right": Data {
        luminosity: [
            Some("20"),
            Some("40"),
            None
        ],
        color: [
            None,
            Some("(255,0,0)"),
            Some("(0,0,0)")
        ]
    },
    "top": Data {
        luminosity: [
            Some("10")
        ],
        color: [
            None
        ]
    }
}
person Shepmaster    schedule 12.11.2017