Home > OS >  How do I generate a serde_json object from a "." separated text format?
How do I generate a serde_json object from a "." separated text format?

Time:02-21

The Problem

I am trying to generate a json object (with serde) by parsing a custom macro format that looks like this:

Plot.Polar.max: 20
Plot.Polar.min: 0
Plot.Polar.numberlabel: 0101
Plot.Polar.chartname: small-chart
Plot.Polar.Var.1: 
Plot.Polar.Var.2: A label: with T ES[T] #Data

What I get stuck on is how to set the keys for the object. In my old JavaScript code I split on \n, ., and :, had a couple of nested loops, and a reduceRight in the end to create the object like this:

// rowObject equals one row in the old macro format
let rowObject = keys.reduceRight(
  (allKeys, item) => ({ [item]: allKeys }),
  val,
);

My Goal

My goal is to use that json object to generate a highcharts config (json) depending on the keys and values from the custom macro. I want to be able to print just the macro in json format as well hence why I want to convert the macro to json first and not use a separate data structure (though that might be a good idea?). The json I want to produce from the macro is this:

{
  "Plot": {
    "Polar": {
      "max": 20,
      "min": 0
    }
  }
}

What I Have Tried

  • Map::insert though I am not sure how to structure the key string. How do I manage the Map objects in this case?
  • Another solution I see is creating the object from a raw string and merging each rowObject with the main object though this approach feels a bit hacky.

The current loop I have:

// pseudo
// let mut json_macro = new Map();
for row in macro_rows.iter() {
    let row_key_value: Vec<&str> = row.split(':').collect();
    let keys = row_key_value[0];
    let value = row_key_value[1];
    let keys_split: Vec<&str> = keys.split('.').collect();

    for key in keys_split.iter() {
        // TODO: accumulate a objects to row_object
    }
    // TODO: insert row_object to json_macro
}

The Question

Is it possible to do something like reduceRight in JavaScript or something similar in rust?

Update

I realized that I will have to treat all values as strings because it is impossible to know if a number is a string or not. What worked in the end was the solution @gizmo provided.

CodePudding user response:

To insert your row into json_macro you can fold keys_split from the left and insert every key into the top-level object:

let row_key_value: Vec<&str> = row.split(':').collect();
let keys = row_key_value[0];
let value: Value = serde_json::from_str(row_key_value[1]).unwrap();
let keys_split: Vec<&str> = keys.split('.').collect();
keys_split[..keys_split.len() - 1]
    .iter()
    .fold(&mut json_macro, |object, &key| {
        object
            .entry(key)
            .or_insert(Map::new().into())
            .as_object_mut()
            .unwrap()
    })
.insert(keys_split.last().unwrap().to_string(), value);

A couple things to note here about unwrap()s:

  • from_str(...).unwrap(): I parse val as a JSON object here. This might not be what you want. Maybe instead you want str::parse::<i32> or something else. In any case, this parsing might fail.
  • .as_object_mut().unwrap(): This will explode if the input redefines a key like
Plot.Polar: 0
Plot.Polar.max: 20
  • The other way around, you probably want to handle the case where the key is already defined as an object.
  • keys_split.last().unwrap() won't fail but you might want to check if it's the empty string
  • Related