zhiburt / tabled

An easy to use library for pretty print tables of Rust structs and enums.
MIT License
1.94k stars 77 forks source link

Collated JSON Tables #420

Open abhillman opened 1 month ago

abhillman commented 1 month ago
use json_to_table::json_to_table;
use serde::Serialize;

#[derive(Serialize)]
struct Thing {
    f1: String,
    f2: String,
    f3: String,
}

fn main() {
    let t1 = Thing {
        f1: "a".to_string(),
        f2: "b".to_string(),
        f3: "c".to_string(),
    };

    let t2 = Thing {
        f1: "d".to_string(),
        f2: "e".to_string(),
        f3: "f".to_string(),
    };

    let t3 = Thing {
        f1: "g".to_string(),
        f2: "h".to_string(),
        f3: "i".to_string(),
    };

    let ts = vec![t1, t2, t3];

    let v = serde_json::to_value(ts).unwrap();
    let jt = json_to_table(&v).into_table();
    let mut tb = tabled::builder::Builder::from(jt);
    let table = tb.index().column(0).transpose().build();

    println!("{}", table.to_string())
}

Results in:

+--------------+--------------+--------------+
| +----+-----+ | +----+-----+ | +----+-----+ |
| | f1 |  a  | | | f1 |  d  | | | f1 |  g  | |
| +----+-----+ | +----+-----+ | +----+-----+ |
| | f2 |  b  | | | f2 |  e  | | | f2 |  h  | |
| +----+-----+ | +----+-----+ | +----+-----+ |
| | f3 |  c  | | | f3 |  f  | | | f3 |  i  | |
| +----+-----+ | +----+-----+ | +----+-----+ |
+--------------+--------------+--------------+

Is there a way to get f1, f2, f3 as the header and values as rows?

zhiburt commented 1 month ago

Hey @abhillman

So I am sure there are many ways to do that, depending on your needs, but the easiest based on your example is probably the next one.

use json_to_table::{json_to_table, Orientation};
use serde::Serialize;

#[derive(Serialize)]
struct Thing {
    f1: String,
    f2: String,
    f3: String,
}

fn main() {
    let t1 = Thing {
        f1: "a".to_string(),
        f2: "b".to_string(),
        f3: "c".to_string(),
    };

    let t2 = Thing {
        f1: "d".to_string(),
        f2: "e".to_string(),
        f3: "f".to_string(),
    };

    let t3 = Thing {
        f1: "g".to_string(),
        f2: "h".to_string(),
        f3: "i".to_string(),
    };

    let data = vec![t1, t2, t3];
    let json = serde_json::to_value(data).unwrap();

    let table = json_to_table(&json)
        .object_orientation(Orientation::Row)
        .array_orientation(Orientation::Row)
        .into_table();

    println!("{}", table)
}
+---------------------+---------------------+---------------------+
| +-----+-----+-----+ | +-----+-----+-----+ | +-----+-----+-----+ |
| | f1  | f2  | f3  | | | f1  | f2  | f3  | | | f1  | f2  | f3  | |
| +-----+-----+-----+ | +-----+-----+-----+ | +-----+-----+-----+ |
| |  a  |  b  |  c  | | |  d  |  e  |  f  | | |  g  |  h  |  i  | |
| +-----+-----+-----+ | +-----+-----+-----+ | +-----+-----+-----+ |
+---------------------+---------------------+---------------------+
zhiburt commented 1 month ago

Just in case you can customize it for example like this

use json_to_table::{json_to_table, Orientation};
use serde::Serialize;
use tabled::settings::{Padding, Style};

#[derive(Serialize)]
struct Thing {
    f1: String,
    f2: String,
    f3: String,
}

fn main() {
    let t1 = Thing {
        f1: "a".to_string(),
        f2: "b".to_string(),
        f3: "c".to_string(),
    };

    let t2 = Thing {
        f1: "d".to_string(),
        f2: "e".to_string(),
        f3: "f".to_string(),
    };

    let t3 = Thing {
        f1: "g".to_string(),
        f2: "h".to_string(),
        f3: "i".to_string(),
    };

    let data = vec![t1, t2, t3];
    let json = serde_json::to_value(data).unwrap();

    let mut table = json_to_table(&json)
        .object_orientation(Orientation::Row)
        .array_orientation(Orientation::Row)
        .with(Style::modern_rounded())
        .into_table();

    table.with(Style::modern()).with(Padding::zero());

    println!("{}", table)
}
┌───────────────────┬───────────────────┬───────────────────┐
│╭─────┬─────┬─────╮│╭─────┬─────┬─────╮│╭─────┬─────┬─────╮│
││ f1  │ f2  │ f3  │││ f1  │ f2  │ f3  │││ f1  │ f2  │ f3  ││
│├─────┼─────┼─────┤│├─────┼─────┼─────┤│├─────┼─────┼─────┤│
││  a  │  b  │  c  │││  d  │  e  │  f  │││  g  │  h  │  i  ││
│╰─────┴─────┴─────╯│╰─────┴─────┴─────╯│╰─────┴─────┴─────╯│
└───────────────────┴───────────────────┴───────────────────┘
abhillman commented 1 month ago

Thanks so much for your reply. Is there a minimal way to do something like the following? Of course, I can definitely hack something together... but:

+---------------------+
| +-----+-----+-----+ |
| | f1  | f2  | f3  | |
| +-----+-----+-----+ |
| |  a  |  b  |  c  | |
| |  d  |  e  |  f  | |
| |  g  |  h  |  i  | |
| +-----+-----+-----+ |
+---------------------+
zhiburt commented 1 month ago

You're right there plenty (and I mean really a lot) of ways to do that.

Somehow I tried to stick to basic tabled without use of json_to_table.

use std::{collections::HashMap, iter::FromIterator};

use tabled::{
    builder::Builder,
    settings::{
        style::{HorizontalLine, On},
        Style,
    },
};

// well... yes a bit complex but it's case we use CONST
const STYLE: Style<On, On, On, On, (), On, 1, 0> = Style::modern()
    .remove_horizontal()
    .horizontals([(1, HorizontalLine::inherit(Style::modern()))]);

fn main() {
    let json = serde_json::json!(
        [
            { "field1": "1" },
            { "field1": "2", "field2": 4 },
            { "field1": "3", "field2": 4 },
            { "field1": "4", "field3": 10 },
        ]
    );

    let map = json
        .as_array()
        .unwrap()
        .iter()
        .map(|value| value.as_object().unwrap())
        .fold(HashMap::new(), |mut acc, row| {
            for (key, element) in row {
                acc.entry(key.to_string())
                    .or_insert_with(Vec::new)
                    .push(element.to_string());
            }

            acc
        });

    let mut builder = Builder::from_iter(map.values());
    builder.insert_column(0, map.keys());

    let builder = builder.index().column(0).transpose();

    let mut table = builder.build();
    table.with(STYLE);

    println!("{}", table);
}
┌────────┬────────┬────────┐
│ field1 │ field2 │ field3 │
├────────┼────────┼────────┤
│ "1"    │ 4      │ 10     │
│ "2"    │ 4      │        │
│ "3"    │        │        │
│ "4"    │        │        │
└────────┴────────┴────────┘

But maybe it's common behaivour (I mean your json example and it worth to add some function to json_to_table to parse it right away. )

zhiburt commented 1 month ago

My example could be done pretty affective if switched to iterative approach rather then collecting a HashMap. You could build Builder step by step.

Also as a different approach there could be usage for PoolTable and TableValue.