New --collection flag to json_encode command which encodes maps/arrays instead of variables #175

This commit is contained in:
sagie gur ari 2021-07-09 22:19:15 +00:00
parent 7d231e16da
commit 1cc459defa
5 changed files with 149 additions and 11 deletions

View File

@ -3,6 +3,7 @@
### v0.8.4
* New --collection flag to json_parse command which returns maps/arrays instead of variables #175
* New --collection flag to json_encode command which encodes maps/arrays instead of variables #175
### v0.8.3

View File

@ -4105,11 +4105,14 @@ string = json_encode var_name
```
This function will encode all variables, starting from the root variable as a JSON string.<br>
Since duckscript is untyped, all boolean and numeric values will be encoded as strings.
Since duckscript is untyped, all boolean and numeric values will be encoded as strings.<br>
If --collection is passed, the provided value is considered as string or a map/array handle which is used to fetch
the tree data and create the json string.
#### Parameters
The root variable name
* Option --collection flag to make the encoding use the maps/arrays and values
* The root variable name (or a handle/value in case --collection is provided)
#### Return Value
@ -4118,8 +4121,13 @@ The JSON string
#### Examples
```sh
# will parse and encode to plain variables
package = json_parse "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
jsonstring = json_encode package
# will parse and encode to maps/arrays
package = json_parse --collection "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
jsonstring = json_encode --collection ${package}
```

View File

@ -3,11 +3,14 @@ string = json_encode var_name
```
This function will encode all variables, starting from the root variable as a JSON string.<br>
Since duckscript is untyped, all boolean and numeric values will be encoded as strings.
Since duckscript is untyped, all boolean and numeric values will be encoded as strings.<br>
If --collection is passed, the provided value is considered as string or a map/array handle which is used to fetch
the tree data and create the json string.
#### Parameters
The root variable name
* Option --collection flag to make the encoding use the maps/arrays and values
* The root variable name (or a handle/value in case --collection is provided)
#### Return Value
@ -16,6 +19,11 @@ The JSON string
#### Examples
```sh
# will parse and encode to plain variables
package = json_parse "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
jsonstring = json_encode package
# will parse and encode to maps/arrays
package = json_parse --collection "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
jsonstring = json_encode --collection ${package}
```

View File

@ -1,10 +1,11 @@
use crate::sdk::std::json::OBJECT_VALUE;
use crate::utils::pckg;
use crate::utils::state::get_handles_sub_state;
use duckscript::types::command::{Command, CommandResult, Commands};
use duckscript::types::instruction::Instruction;
use duckscript::types::runtime::StateValue;
use serde_json::map::Map;
use serde_json::Value;
use serde_json::{Number, Value};
use std::collections::{HashMap, HashSet};
#[cfg(test)]
@ -92,7 +93,10 @@ fn encode_any(name: &str, values: &HashMap<&str, &str>) -> Result<Value, String>
}
}
fn encode(name: &str, variables: &HashMap<String, String>) -> Result<String, String> {
fn encode_from_variables(
name: &str,
variables: &HashMap<String, String>,
) -> Result<String, String> {
let mut object_variables: HashMap<&str, &str> = HashMap::new();
for (key, value) in variables {
@ -111,6 +115,68 @@ fn encode(name: &str, variables: &HashMap<String, String>) -> Result<String, Str
}
}
fn encode_from_state_value(
state_value: &StateValue,
state: &HashMap<String, StateValue>,
) -> Result<Value, String> {
match state_value {
StateValue::Boolean(value) => Ok(Value::Bool(*value)),
StateValue::Number(value) => Ok(Value::Number(Number::from(*value))),
StateValue::UnsignedNumber(value) => Ok(Value::Number(Number::from(*value))),
StateValue::Number32Bit(value) => Ok(Value::Number(Number::from(*value))),
StateValue::UnsignedNumber32Bit(value) => Ok(Value::Number(Number::from(*value))),
StateValue::Number64Bit(value) => Ok(Value::Number(Number::from(*value))),
StateValue::UnsignedNumber64Bit(value) => Ok(Value::Number(Number::from(*value))),
StateValue::String(value) => match state.get(value) {
Some(sub_state_value) => encode_from_state_value(sub_state_value, state),
None => Ok(Value::String(value.to_string())),
},
StateValue::List(list) => {
let mut items = vec![];
for item in list {
match encode_from_state_value(item, state) {
Ok(item_value) => {
items.push(item_value);
}
Err(error) => return Err(error),
};
}
Ok(Value::Array(items))
}
StateValue::SubState(sub_state) => {
let mut items = Map::new();
for (key, value) in sub_state {
match encode_from_state_value(value, state) {
Ok(item_value) => {
items.insert(key.to_string(), item_value);
}
Err(error) => return Err(error),
}
}
Ok(Value::Object(items))
}
StateValue::ByteArray(_) => Err("Unsupported value type.".to_string()),
StateValue::Set(_) => Err("Unsupported value type.".to_string()),
StateValue::Any(_) => Err("Unsupported value type.".to_string()),
}
}
fn encode_from_state(value: &str, state: &HashMap<String, StateValue>) -> Result<String, String> {
let json_value = match state.get(value) {
Some(state_value) => encode_from_state_value(state_value, state),
None => Ok(Value::String(value.to_string())),
};
match json_value {
Ok(json_value_obj) => Ok(json_value_obj.to_string()),
Err(error) => Err(error.to_string()),
}
}
#[derive(Clone)]
pub(crate) struct CommandImpl {
package: String,
@ -140,7 +206,7 @@ impl Command for CommandImpl {
fn run_with_context(
&self,
arguments: Vec<String>,
_state: &mut HashMap<String, StateValue>,
state: &mut HashMap<String, StateValue>,
variables: &mut HashMap<String, String>,
_output_variable: Option<String>,
_instructions: &Vec<Instruction>,
@ -150,9 +216,24 @@ impl Command for CommandImpl {
if arguments.is_empty() {
CommandResult::Error("No JSON root variable name provided.".to_string())
} else {
match encode(&arguments[0], variables) {
Ok(output) => CommandResult::Continue(Some(output)),
Err(error) => CommandResult::Error(error),
let (start_index, as_state) = if arguments.len() > 1 && arguments[0] == "--collection" {
(1, true)
} else {
(0, false)
};
if as_state {
let state = get_handles_sub_state(state);
match encode_from_state(&arguments[start_index], state) {
Ok(output) => CommandResult::Continue(Some(output)),
Err(error) => CommandResult::Error(error),
}
} else {
match encode_from_variables(&arguments[start_index], variables) {
Ok(output) => CommandResult::Continue(Some(output)),
Err(error) => CommandResult::Error(error),
}
}
}
}

View File

@ -52,7 +52,7 @@ fn test_simple_types
assert_false ${defined}
end
fn test_all_types
fn test_all_types_as_vars
jsonstring = set "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
package = json_parse ${jsonstring}
@ -98,3 +98,43 @@ fn test_all_types
assert_eq ${package2.keywords[1].directories.test} spec
end
fn test_all_types_as_collections
jsonstring = set "{\"name\": \"my package\", \"version\": 1, \"publish\": false, \"keywords\": [\"test1\", \"test2\"], \"directories\": {\"test\": \"spec\"}}"
package = json_parse --collection ${jsonstring}
name = map_get ${package} name
assert_eq ${name} "my package"
version = map_get ${package} version
assert_eq ${version} 1
public = map_get ${package} public
assert_false ${public}
keywords_handle = map_get ${package} keywords
length = array_length ${keywords_handle}
assert_eq ${length} 2
value = array_get ${keywords_handle} 0
assert_eq ${value} test1
value = array_get ${keywords_handle} 1
assert_eq ${value} test2
directories = map_get ${package} directories
directory = map_get ${directories} test
assert_eq ${directory} spec
map_put ${package} name "my package 2"
name = map_get ${package} name
assert_eq ${name} "my package 2"
update_jsonstring = json_encode --collection ${package}
release --recursive ${package}
package2 = json_parse ${update_jsonstring}
assert_eq ${package2} "[OBJECT]"
assert_eq ${package2.name} "my package 2"
assert_eq ${package2.version} 1
assert_eq ${package2.publish} false
assert_eq ${package2.keywords.length} 2
assert_eq ${package2.keywords[0]} test1
assert_eq ${package2.keywords[1]} test2
assert_eq ${package2.directories.test} spec
end