dimanche 10 novembre 2019

How to design a Map which can be sorted based on one of the values?

Why this question exists?

I am writing rabbithole-rs, which is a JSON:API implementation based on Rust. But while designing the sorting/pagination features, I meet a big problem on on designing the attribute fields.

Intro JSON:API soring/pagination briefly

There is a attributes fields in JSON:API's resource object, which is a HashMap<String, serde_json::Value> or HashMap<String, Box<Any>>, where the key is attribute name, and the value is the attribute's value.

And Vec<Resource> object has a sort(attribute_name: &str) method, which can sort a bunch of Resource based on one of the attribute.

A brief layout of Resource

pub struct Resource {
    pub ty: String, // The type of `Resource`, `sort` method can only effect on the same type of resources
    pub id: String, // Every `Resource` has an unique id
    pub attr1: dyn Ord + Serialize + Deserialize,
    ... <more attrs>
}

Where:

  • Each attr should implement at least three traits:
    • Ord to compare
    • Serialize to convert into serde_json::Value
    • Deserialize to convert from serde_json::Value

What the problem is?

When getting a Resource, I have to extract all of the attribute fields and put them into a HashMap, like: HashMap<String, serde_json::Value> or HashMap<String, Box<Any>>. But both of them lose the trait info of the attributes, so I cannot compare two attribute item with the same name.

A brief demo, please!

Sure! Here you go!

#[macro_use]
extern crate serde_derive;

use serde::{Deserialize, Serialize};
use std::any::Any;
use std::collections::HashMap;

/// This is a struct field
#[derive(Debug, Serialize, Deserialize, Ord, PartialOrd, PartialEq, Eq)]
pub struct Name {
    pub first_name: String,
    pub last_name: String,
}

/// Resource object
#[derive(Debug, Serialize, Deserialize, Ord, PartialOrd, PartialEq, Eq)]
pub struct Resource {
    pub ty: String,
    pub id: String,
    pub age: i32, // attr 1
    pub name: Name, // another attr
}

fn main() {
    let item1 = Resource {
        ty: "human".into(),
        id: "id1".to_string(),
        age: 1,
        name: Name {
            first_name: "first1".to_string(),
            last_name: "last1".to_string(),
        },
    };

    // **This is the first attributes HashMap**
    let mut map_item1: HashMap<&str, (&str, Box<dyn Any>)> = Default::default();
    map_item1.insert("age", ("i32", Box::new(item1.age)));
    map_item1.insert("name", ("Name", Box::new(item1.name)));

    let item2 = Resource {
        ty: "human".into(),
        id: "id2".to_string(),
        age: 2,
        name: Name {
            first_name: "first2".to_string(),
            last_name: "last2".to_string(),
        },
    };

    // **This is the second attributes HashMap**
    let mut map_item2: HashMap<&str, (&str, Box<dyn Any>)> = Default::default();
    map_item2.insert("age", ("i32", Box::new(item2.age)));
    map_item2.insert("name", ("Name", Box::new(item2.name)));

    // TODO: NEED TO BE IMPLEMENTED
    for k in map_item1.keys() {
        println!(
            "key: {key}, item1.{key} < item2.{key}? {res}",
            key = k,
            res = map_item1.get(k).unwrap().1.cmp(map_item2.get(k).unwrap().1)
        )
    }
}




Aucun commentaire:

Enregistrer un commentaire