diff --git a/crates/e2e-move-tests/src/tests/args.rs b/crates/e2e-move-tests/src/tests/args.rs index d8ca9155..4ed1def4 100644 --- a/crates/e2e-move-tests/src/tests/args.rs +++ b/crates/e2e-move-tests/src/tests/args.rs @@ -814,10 +814,7 @@ fn biguint_bigdecimal() { vec![acc], str::parse(entry).unwrap(), vec![], - vec![ - r#""100""#.to_string(), - r#""100""#.to_string(), - ], + vec![r#""100""#.to_string(), r#""100""#.to_string()], ) .unwrap(); diff --git a/crates/e2e-move-tests/src/tests/move_unit.rs b/crates/e2e-move-tests/src/tests/move_unit.rs index 7749bcb5..c18a46de 100644 --- a/crates/e2e-move-tests/src/tests/move_unit.rs +++ b/crates/e2e-move-tests/src/tests/move_unit.rs @@ -4,7 +4,10 @@ use crate::test_utils::mock_chain::{BlankAPIImpl, BlankTableViewImpl}; -use initia_move_gas::{InitiaGasParameters, InitialGasSchedule, MiscGasParameters, NativeGasParameters}; +use initia_move_compiler::unit_test_factory::InitiaUnitTestFactory; +use initia_move_gas::{ + InitiaGasParameters, InitialGasSchedule, MiscGasParameters, NativeGasParameters, +}; use initia_move_natives::{ account::NativeAccountContext, all_natives, block::NativeBlockContext, code::NativeCodeContext, cosmos::NativeCosmosContext, event::NativeEventContext, oracle::NativeOracleContext, @@ -12,7 +15,6 @@ use initia_move_natives::{ transaction_context::NativeTransactionContext, }; use initia_move_types::metadata; -use initia_move_compiler::unit_test_factory::InitiaUnitTestFactory; use move_cli::base::test::{run_move_unit_tests_with_factory, UnitTestResult}; use move_core_types::effects::ChangeSet; diff --git a/crates/e2e-move-tests/src/tests/view_output.rs b/crates/e2e-move-tests/src/tests/view_output.rs index 26f907b7..56487865 100644 --- a/crates/e2e-move-tests/src/tests/view_output.rs +++ b/crates/e2e-move-tests/src/tests/view_output.rs @@ -46,7 +46,8 @@ fn test_view_output() { module: module_name, name: struct_name, type_args: vec![] - })).to_string(), + })) + .to_string(), "{\"arg\":\"hello world\",\"type_arg\":\"u256\"}".to_string() )]) .into_inner(), diff --git a/crates/json/src/json_to_move.rs b/crates/json/src/json_to_move.rs index d99a3136..dcb3800b 100644 --- a/crates/json/src/json_to_move.rs +++ b/crates/json/src/json_to_move.rs @@ -137,6 +137,20 @@ fn convert_json_value_to_move_value( .ok_or_else(deserialization_error)?; let full_name = format!("{}::{}", st.module.short_str_lossless(), st.name); match full_name.as_str() { + // JSONValue and JSONObject are not supported as entry function arguments + // + // "0x1::json::JSONValue" => MoveValue::vector_u8( + // serde_json::to_vec(&json_val).map_err(deserialization_error_with_msg)?, + // ), + // "0x1::json::JSONObject" => { + // let json_obj = json_val.as_object().ok_or_else(deserialization_error)?.to_owned(); + // let elems = json_obj.into_iter().map(|(k, v)| { + // let key = k.into_bytes(); + // let value = serde_json::to_vec(&v).map_err(deserialization_error_with_msg)?; + // Ok(MoveValue::Struct(MoveStruct::new(vec![MoveValue::vector_u8(key), MoveValue::vector_u8(value)]))) + // }).collect::>>()?; + // MoveValue::Vector(elems) + // }, "0x1::string::String" => MoveValue::vector_u8( json_val.as_str().ok_or_else(deserialization_error)?.into(), ), @@ -780,10 +794,9 @@ mod json_arg_testing { #[test] fn test_deserialize_json_args_big_uint() { let mut mock_state = mock_state(); - mock_state.structs.insert( - StructNameIndex(0), - Arc::new(for_test("biguint", "BigUint")), - ); + mock_state + .structs + .insert(StructNameIndex(0), Arc::new(for_test("biguint", "BigUint"))); let state_view = StateViewImpl::new(&mock_state); @@ -823,7 +836,12 @@ mod json_arg_testing { assert_eq!( result, - bcs::to_bytes(&BigUint::from_u128(1234567u128 * (1e14 as u128)).unwrap().to_bytes_le()).unwrap() + bcs::to_bytes( + &BigUint::from_u128(1234567u128 * (1e14 as u128)) + .unwrap() + .to_bytes_le() + ) + .unwrap() ); // invalid negative diff --git a/crates/json/src/json_to_value.rs b/crates/json/src/json_to_value.rs index 2764bc42..dd99e1a7 100644 --- a/crates/json/src/json_to_value.rs +++ b/crates/json/src/json_to_value.rs @@ -110,6 +110,28 @@ pub fn convert_json_value_to_value( let full_name = format!("{}::{}", type_.module_id().short_str_lossless(), type_.name); match full_name.as_str() { + "0x1::json::JSONValue" => Value::struct_(Struct::pack(vec![Value::vector_u8( + serde_json::to_vec(&json_val).map_err(deserialization_error_with_msg)?, + )])), + "0x1::json::JSONObject" => { + let json_obj = json_val + .as_object() + .ok_or_else(deserialization_error)? + .to_owned(); + let elems = json_obj + .into_iter() + .map(|(k, v)| { + let key = k.into_bytes(); + let value = serde_json::to_vec(&v) + .map_err(deserialization_error_with_msg)?; + Ok(Value::struct_(Struct::pack(vec![ + Value::vector_u8(key), + Value::vector_u8(value), + ]))) + }) + .collect::>>()?; + Value::struct_(Struct::pack(vec![Value::vector_for_testing_only(elems)])) + } "0x1::string::String" => Value::struct_(Struct::pack(vec![Value::vector_u8( json_val .as_str() @@ -598,4 +620,80 @@ mod json_arg_testing { let arg = b"\"-123.4567\""; _ = deserialize_json_to_value(&layout, arg).unwrap_err(); } + + #[test] + fn test_deserialize_json_to_value_json_value() { + let layout = MoveTypeLayout::Struct(MoveStructLayout::with_types( + StructTag { + address: AccountAddress::ONE, + module: ident_str!("json").into(), + name: ident_str!("JSONValue").into(), + type_args: vec![], + }, + vec![MoveFieldLayout { + name: ident_str!("value").into(), + layout: MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), + }], + )); + + let arg = b"\"123\""; + let result = deserialize_json_to_value(&layout, arg).unwrap(); + assert!(result + .equals(&Value::struct_(Struct::pack(vec![Value::vector_u8( + b"\"123\"".to_vec() + )]))) + .unwrap()); + } + + #[test] + fn test_deserialize_json_to_value_json_object() { + let layout = MoveTypeLayout::Struct(MoveStructLayout::with_types( + StructTag { + address: AccountAddress::ONE, + module: ident_str!("json").into(), + name: ident_str!("JSONObject").into(), + type_args: vec![], + }, + vec![MoveFieldLayout { + name: ident_str!("elems").into(), + layout: MoveTypeLayout::Vector(Box::new(MoveTypeLayout::Struct( + MoveStructLayout::with_types( + StructTag { + address: AccountAddress::ONE, + module: ident_str!("json").into(), + name: ident_str!("Element").into(), + type_args: vec![], + }, + vec![ + MoveFieldLayout { + name: ident_str!("key").into(), + layout: MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), + }, + MoveFieldLayout { + name: ident_str!("value").into(), + layout: MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), + }, + ], + ), + ))), + }], + )); + + let arg = b"{\"key1\": \"value1\", \"key2\": \"value2\"}"; + let result = deserialize_json_to_value(&layout, arg).unwrap(); + assert!(result + .equals(&Value::struct_(Struct::pack(vec![ + Value::vector_for_testing_only(vec![ + Value::struct_(Struct::pack(vec![ + Value::vector_u8(b"key1".to_vec()), + Value::vector_u8(b"\"value1\"".to_vec()) + ])), + Value::struct_(Struct::pack(vec![ + Value::vector_u8(b"key2".to_vec()), + Value::vector_u8(b"\"value2\"".to_vec()) + ])) + ]) + ]))) + .unwrap()); + } } diff --git a/crates/json/src/move_to_json.rs b/crates/json/src/move_to_json.rs index f204d0dd..737b54dc 100644 --- a/crates/json/src/move_to_json.rs +++ b/crates/json/src/move_to_json.rs @@ -81,7 +81,11 @@ fn convert_move_value_to_json_value(val: &MoveValue, depth: usize) -> VMResult VMResult VMResult> { + match val { + MoveValue::Vector(bytes_val) => bytes_val + .iter() + .map(|byte_val| match byte_val { + MoveValue::U8(byte) => Ok(*byte), + _ => Err(deserialization_error_with_msg("Expected U8 in vector")), + }) + .collect::>>(), + _ => Err(deserialization_error_with_msg("Expected vector of U8s")), + } +} + +fn convert_json_value_to_json_value(val: &MoveValue) -> VMResult { + let bz = bytes_from_move_value(val)?; + serde_json::from_slice(&bz).map_err(deserialization_error_with_msg) +} + +fn convert_json_object_to_json_value(val: &MoveValue) -> VMResult { + let elems = match val { + MoveValue::Vector(elems) => elems + .iter() + .map(|elem| match elem { + MoveValue::Struct( + MoveStruct::WithTypes { type_: _, fields } + | MoveStruct::WithFields(fields) + | MoveStruct::WithVariantFields(_, _, fields), + ) => { + let key = + std::str::from_utf8(&bytes_from_move_value(&fields.first().unwrap().1)?) + .map_err(deserialization_error_with_msg)? + .to_string(); + let val = convert_json_value_to_json_value(&fields.get(1).unwrap().1)?; + + Ok((key, val)) + } + _ => unreachable!(), + }) + .collect::>>()?, + _ => unreachable!(), + }; + + Ok(JSONValue::Object(elems)) +} + fn convert_string_to_json_value(val: &MoveValue) -> VMResult { let bz: Vec = match val { MoveValue::Vector(bytes_val) => bytes_val @@ -232,6 +281,18 @@ fn convert_object_to_json_value(val: &MoveValue) -> VMResult { } // check functions +fn is_json_value(type_: &StructTag) -> bool { + type_.address == CORE_CODE_ADDRESS + && type_.module.as_str() == "json" + && type_.name.as_str() == "JSONValue" +} + +fn is_json_object(type_: &StructTag) -> bool { + type_.address == CORE_CODE_ADDRESS + && type_.module.as_str() == "json" + && type_.name.as_str() == "JSONObject" +} + fn is_utf8_string(type_: &StructTag) -> bool { type_.address == CORE_CODE_ADDRESS && type_.module.as_str() == "string" @@ -453,5 +514,105 @@ mod move_to_json_tests { }); let val = convert_move_value_to_json_value(&mv, 1).unwrap(); assert_eq!(val, json!(addr.to_hex_literal())); + + // json value + let mv = MoveValue::Struct(MoveStruct::WithTypes { + type_: StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("json").into(), + name: ident_str!("JSONValue").into(), + type_args: vec![], + }, + fields: vec![( + ident_str!("val").into(), + MoveValue::Vector(vec![ + MoveValue::U8(34), + MoveValue::U8(109), + MoveValue::U8(111), + MoveValue::U8(118), + MoveValue::U8(101), + MoveValue::U8(34), + ]), + )], + }); + let val = convert_move_value_to_json_value(&mv, 1).unwrap(); + assert_eq!(val, json!("move")); + + // json object + let mv = MoveValue::Struct(MoveStruct::WithTypes { + type_: StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("json").into(), + name: ident_str!("JSONObject").into(), + type_args: vec![], + }, + fields: vec![( + ident_str!("elems").into(), + MoveValue::Vector(vec![ + MoveValue::Struct(MoveStruct::WithTypes { + type_: StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("json").into(), + name: ident_str!("Element").into(), + type_args: vec![], + }, + fields: vec![ + ( + ident_str!("key").into(), + MoveValue::Vector(vec![ + MoveValue::U8(109), + MoveValue::U8(111), + MoveValue::U8(118), + MoveValue::U8(101), + ]), + ), + ( + ident_str!("value").into(), + MoveValue::Vector(vec![ + MoveValue::U8(34), + MoveValue::U8(109), + MoveValue::U8(111), + MoveValue::U8(118), + MoveValue::U8(101), + MoveValue::U8(34), + ]), + ), + ], + }), + MoveValue::Struct(MoveStruct::WithTypes { + type_: StructTag { + address: CORE_CODE_ADDRESS, + module: ident_str!("json").into(), + name: ident_str!("Element").into(), + type_args: vec![], + }, + fields: vec![ + ( + ident_str!("key").into(), + MoveValue::Vector(vec![MoveValue::U8(102)]), + ), + ( + ident_str!("value").into(), + MoveValue::Vector(vec![ + MoveValue::U8(110), + MoveValue::U8(117), + MoveValue::U8(108), + MoveValue::U8(108), + ]), + ), + ], + }), + ]), + )], + }); + + let val = convert_move_value_to_json_value(&mv, 1).unwrap(); + assert_eq!( + val, + json!({ + "move": json!("move"), + "f": json!(null), + }) + ); } } diff --git a/crates/natives/src/query.rs b/crates/natives/src/query.rs index 1648077a..ad335d60 100644 --- a/crates/natives/src/query.rs +++ b/crates/natives/src/query.rs @@ -216,7 +216,10 @@ pub fn make_all( #[cfg(feature = "testing")] ("set_query_response", native_test_only_set_query_response), #[cfg(feature = "testing")] - ("unset_query_response", native_test_only_unset_query_response), + ( + "unset_query_response", + native_test_only_unset_query_response, + ), ]; builder.make_named_natives(natives) diff --git a/crates/natives/src/staking.rs b/crates/natives/src/staking.rs index 7299535a..41117371 100644 --- a/crates/natives/src/staking.rs +++ b/crates/natives/src/staking.rs @@ -56,7 +56,8 @@ pub struct NativeStakingContext<'a> { api: &'a dyn StakingAPI, staking_data: StakingData, #[cfg(feature = "testing")] - share_ratios: BTreeMap, BTreeMap>, + share_ratios: + BTreeMap, BTreeMap>, } // =========================================================================================== diff --git a/crates/types/src/staking_change_set.rs b/crates/types/src/staking_change_set.rs index aebdf237..acbca505 100644 --- a/crates/types/src/staking_change_set.rs +++ b/crates/types/src/staking_change_set.rs @@ -22,7 +22,7 @@ pub struct StakingChangeSet( BTreeMap< AccountAddress, ( - u64, /* delegation amount */ + u64, /* delegation amount */ String, /* undelegation share amount */ ), >, @@ -30,7 +30,9 @@ pub struct StakingChangeSet( ); impl StakingChangeSet { - pub fn new(map: BTreeMap, BTreeMap>) -> StakingChangeSet { + pub fn new( + map: BTreeMap, BTreeMap>, + ) -> StakingChangeSet { Self(map) } diff --git a/crates/vm/src/verifier/transaction_arg_validation.rs b/crates/vm/src/verifier/transaction_arg_validation.rs index cb950dc9..e5f3e2bb 100644 --- a/crates/vm/src/verifier/transaction_arg_validation.rs +++ b/crates/vm/src/verifier/transaction_arg_validation.rs @@ -98,7 +98,7 @@ pub(crate) static ALLOWED_STRUCTS: ConstructorMap = Lazy::new(|| { ), func_name: ident_str!("from_scaled_le_bytes"), }, - ) + ), ] .into_iter() .map(|(s, validator)| (s.to_string(), validator)) diff --git a/libcompiler/src/interface.rs b/libcompiler/src/interface.rs index a9734b5f..5b2dcb23 100644 --- a/libcompiler/src/interface.rs +++ b/libcompiler/src/interface.rs @@ -64,7 +64,8 @@ pub extern "C" fn coverage_summary_move_package( ) -> UnmanagedVector { let compiler_args: CompilerArguments = bcs::from_bytes(compiler_args_paylod.read().unwrap()).unwrap(); - let coverage_opt: CompilerCoverageSummaryOptions = bcs::from_bytes(coverage_opt_payload.read().unwrap()).unwrap(); + let coverage_opt: CompilerCoverageSummaryOptions = + bcs::from_bytes(coverage_opt_payload.read().unwrap()).unwrap(); let cmd = Command::Coverage(coverage_opt.into()); let res = catch_unwind(AssertUnwindSafe(move || { @@ -84,7 +85,8 @@ pub extern "C" fn coverage_source_move_package( ) -> UnmanagedVector { let compiler_args: CompilerArguments = bcs::from_bytes(compiler_args_paylod.read().unwrap()).unwrap(); - let coverage_opt: CompilerCoverageSourceOptions = bcs::from_bytes(coverage_opt_payload.read().unwrap()).unwrap(); + let coverage_opt: CompilerCoverageSourceOptions = + bcs::from_bytes(coverage_opt_payload.read().unwrap()).unwrap(); let cmd = Command::Coverage(coverage_opt.into()); let res = catch_unwind(AssertUnwindSafe(move || { @@ -104,7 +106,8 @@ pub extern "C" fn coverage_bytecode_move_package( ) -> UnmanagedVector { let compiler_args: CompilerArguments = bcs::from_bytes(compiler_args_paylod.read().unwrap()).unwrap(); - let coverage_opt: CompilerCoverageBytecodeOptions = bcs::from_bytes(coverage_opt_payload.read().unwrap()).unwrap(); + let coverage_opt: CompilerCoverageBytecodeOptions = + bcs::from_bytes(coverage_opt_payload.read().unwrap()).unwrap(); let cmd = Command::Coverage(coverage_opt.into()); let res = catch_unwind(AssertUnwindSafe(move || { @@ -124,7 +127,8 @@ pub extern "C" fn docgen_move_package( ) -> UnmanagedVector { let compiler_args: CompilerArguments = bcs::from_bytes(compiler_args_paylod.read().unwrap()).unwrap(); - let docgen_opt: CompilerDocgenOptions = bcs::from_bytes(docgen_opt_payload.read().unwrap()).unwrap(); + let docgen_opt: CompilerDocgenOptions = + bcs::from_bytes(docgen_opt_payload.read().unwrap()).unwrap(); let cmd = Command::Document(docgen_opt.into()); let res: Result<_, Error> = catch_unwind(AssertUnwindSafe(move || { diff --git a/precompile/binaries/minlib/json.mv b/precompile/binaries/minlib/json.mv index 821cc698..88d8b892 100644 Binary files a/precompile/binaries/minlib/json.mv and b/precompile/binaries/minlib/json.mv differ diff --git a/precompile/binaries/minlib/simple_map.mv b/precompile/binaries/minlib/simple_map.mv index b7d5fa8a..7bbd4ca8 100644 Binary files a/precompile/binaries/minlib/simple_map.mv and b/precompile/binaries/minlib/simple_map.mv differ diff --git a/precompile/binaries/stdlib/json.mv b/precompile/binaries/stdlib/json.mv index 821cc698..88d8b892 100644 Binary files a/precompile/binaries/stdlib/json.mv and b/precompile/binaries/stdlib/json.mv differ diff --git a/precompile/binaries/stdlib/simple_map.mv b/precompile/binaries/stdlib/simple_map.mv index b7d5fa8a..7bbd4ca8 100644 Binary files a/precompile/binaries/stdlib/simple_map.mv and b/precompile/binaries/stdlib/simple_map.mv differ diff --git a/precompile/modules/initia_stdlib/sources/json.move b/precompile/modules/initia_stdlib/sources/json.move index dfd2c7bb..0aa2a8a8 100644 --- a/precompile/modules/initia_stdlib/sources/json.move +++ b/precompile/modules/initia_stdlib/sources/json.move @@ -1,5 +1,76 @@ module initia_std::json { - use std::string::String; + use std::vector; + use std::string::{Self, String}; + use std::option::{Self, Option}; + + /// JSONValue is a struct to hold any JSON value which is unknown at compile time. + struct JSONValue has copy, drop { + value: vector + } + + /// JSONObject is a struct to hold any json object which is unknown at compile time. + struct JSONObject has copy, drop { + elems: vector + } + + /// Element is a struct to hold key-value pair in JSON object. + struct Element has copy, drop { + key: vector, + value: vector + } + + /// Unmarshal JSON value to the given type. + public fun unmarshal_json_value(json_value: JSONValue): T { + unmarshal(json_value.value) + } + + /// Get the list of keys from the JSON object. + public fun keys(obj: &JSONObject): vector { + vector::map_ref(&obj.elems, |elem| { + use_elem(elem); + string::utf8(elem.key) + }) + } + + /// Get the value of the given key from the JSON object. + public fun get_elem(obj: &JSONObject, key: String): Option { + let key_bytes = string::bytes(&key); + let (found, idx) = vector::find(&obj.elems, |elem| { + use_elem(elem); + elem.key == *key_bytes + }); + + if (!found) { + return option::none() + }; + + let elem = vector::borrow(&obj.elems, idx); + option::some(unmarshal(elem.value)) + } + + /// Set or overwrite the element in the JSON object. + public fun set_elem(obj: &mut JSONObject, key: String, value: &T) { + let key_bytes = string::bytes(&key); + let (found, idx) = vector::find(&obj.elems, |elem| { + use_elem(elem); + elem.key == *key_bytes + }); + + if (!found) { + vector::push_back(&mut obj.elems, Element { + key: *key_bytes, + value: marshal(value) + }); + } else { + let elem = vector::borrow_mut(&mut obj.elems, idx); + elem.value = marshal(value); + } + } + + // + // (only on compiler v1) for preventing compile error; because of inferring type issue + // + inline fun use_elem(_elem: &Element) {} /// Marshal data to JSON bytes. /// @@ -19,12 +90,6 @@ module initia_std::json { /// NOTE: key `move` is converted to `_move_` native public fun unmarshal(json: vector): T; - #[test_only] - use std::string; - - #[test_only] - use std::option::{Self, Option}; - #[test_only] use std::biguint::{Self, BigUint}; @@ -108,10 +173,29 @@ module initia_std::json { let obj2 = unmarshal(json); let json2 = marshal(&obj2); - assert!( - json2 - == b"{\"@type\":\"/cosmos.gov.v1.MsgVote\",\"a\":\"42\",\"b\":true,\"bigdecimal\":\"0.0123\",\"biguint\":\"42\",\"c\":\"010203\",\"d\":\"0x1\",\"e\":{\"a\":\"42\",\"b\":true,\"c\":\"010203\"},\"f\":null,\"move\":\"move\"}", - 1 - ); + assert!(json2 == json, 1); + + let json_val = unmarshal(json); + let json3 = marshal(&json_val); + assert!(json3 == json, 2); + + let obj3 = unmarshal_json_value(json_val); + let json4 = marshal(&obj3); + assert!(json4 == json, 3); + + let json_obj = unmarshal(json); + let json5 = marshal(&json_obj); + assert!(json5 == json, 4); + + assert!(option::extract(&mut get_elem(&json_obj, string::utf8(b"a"))) == 42, 4); + assert!(option::extract(&mut get_elem(&json_obj, string::utf8(b"b"))) == true, 5); + assert!(option::extract(&mut get_elem>(&json_obj, string::utf8(b"c"))) == vector[1, 2, 3], 6); + assert!(option::extract(&mut get_elem
(&json_obj, string::utf8(b"d"))) == @0x1, 7); + + set_elem(&mut json_obj, string::utf8(b"c"), &string::utf8(b"hello")); + assert!(option::extract(&mut get_elem(&json_obj, string::utf8(b"c"))) == string::utf8(b"hello"), 8); + + let json5 = marshal(&json_obj); + assert!(json5 == b"{\"@type\":\"/cosmos.gov.v1.MsgVote\",\"a\":\"42\",\"b\":true,\"bigdecimal\":\"0.0123\",\"biguint\":\"42\",\"c\":\"hello\",\"d\":\"0x1\",\"e\":{\"a\":\"42\",\"b\":true,\"c\":\"010203\"},\"f\":null,\"move\":\"move\"}", 9); } } diff --git a/precompile/modules/minitia_stdlib/sources/json.move b/precompile/modules/minitia_stdlib/sources/json.move index 095bd2fa..e8166eb7 100644 --- a/precompile/modules/minitia_stdlib/sources/json.move +++ b/precompile/modules/minitia_stdlib/sources/json.move @@ -1,5 +1,76 @@ module minitia_std::json { - use std::string::String; + use std::vector; + use std::string::{Self, String}; + use std::option::{Self, Option}; + + /// JSONValue is a struct to hold any JSON value which is unknown at compile time. + struct JSONValue has copy, drop { + value: vector + } + + /// JSONObject is a struct to hold any json object which is unknown at compile time. + struct JSONObject has copy, drop { + elems: vector + } + + /// Element is a struct to hold key-value pair in JSON object. + struct Element has copy, drop { + key: vector, + value: vector + } + + /// Unmarshal JSON value to the given type. + public fun unmarshal_json_value(json_value: JSONValue): T { + unmarshal(json_value.value) + } + + /// Get the list of keys from the JSON object. + public fun keys(obj: &JSONObject): vector { + vector::map_ref(&obj.elems, |elem| { + use_elem(elem); + string::utf8(elem.key) + }) + } + + /// Get the value of the given key from the JSON object. + public fun get_elem(obj: &JSONObject, key: String): Option { + let key_bytes = string::bytes(&key); + let (found, idx) = vector::find(&obj.elems, |elem| { + use_elem(elem); + elem.key == *key_bytes + }); + + if (!found) { + return option::none() + }; + + let elem = vector::borrow(&obj.elems, idx); + option::some(unmarshal(elem.value)) + } + + /// Set or overwrite the element in the JSON object. + public fun set_elem(obj: &mut JSONObject, key: String, value: &T) { + let key_bytes = string::bytes(&key); + let (found, idx) = vector::find(&obj.elems, |elem| { + use_elem(elem); + elem.key == *key_bytes + }); + + if (!found) { + vector::push_back(&mut obj.elems, Element { + key: *key_bytes, + value: marshal(value) + }); + } else { + let elem = vector::borrow_mut(&mut obj.elems, idx); + elem.value = marshal(value); + } + } + + // + // (only on compiler v1) for preventing compile error; because of inferring type issue + // + inline fun use_elem(_elem: &Element) {} /// Marshal data to JSON bytes. /// @@ -19,12 +90,6 @@ module minitia_std::json { /// NOTE: key `move` is converted to `_move_` native public fun unmarshal(json: vector): T; - #[test_only] - use std::string; - - #[test_only] - use std::option::{Self, Option}; - #[test_only] use std::biguint::{Self, BigUint}; @@ -108,10 +173,29 @@ module minitia_std::json { let obj2 = unmarshal(json); let json2 = marshal(&obj2); - assert!( - json2 - == b"{\"@type\":\"/cosmos.gov.v1.MsgVote\",\"a\":\"42\",\"b\":true,\"bigdecimal\":\"0.0123\",\"biguint\":\"42\",\"c\":\"010203\",\"d\":\"0x1\",\"e\":{\"a\":\"42\",\"b\":true,\"c\":\"010203\"},\"f\":null,\"move\":\"move\"}", - 1 - ); + assert!(json2 == json, 1); + + let json_val = unmarshal(json); + let json3 = marshal(&json_val); + assert!(json3 == json, 2); + + let obj3 = unmarshal_json_value(json_val); + let json4 = marshal(&obj3); + assert!(json4 == json, 3); + + let json_obj = unmarshal(json); + let json5 = marshal(&json_obj); + assert!(json5 == json, 4); + + assert!(option::extract(&mut get_elem(&json_obj, string::utf8(b"a"))) == 42, 4); + assert!(option::extract(&mut get_elem(&json_obj, string::utf8(b"b"))) == true, 5); + assert!(option::extract(&mut get_elem>(&json_obj, string::utf8(b"c"))) == vector[1, 2, 3], 6); + assert!(option::extract(&mut get_elem
(&json_obj, string::utf8(b"d"))) == @0x1, 7); + + set_elem(&mut json_obj, string::utf8(b"c"), &string::utf8(b"hello")); + assert!(option::extract(&mut get_elem(&json_obj, string::utf8(b"c"))) == string::utf8(b"hello"), 8); + + let json5 = marshal(&json_obj); + assert!(json5 == b"{\"@type\":\"/cosmos.gov.v1.MsgVote\",\"a\":\"42\",\"b\":true,\"bigdecimal\":\"0.0123\",\"biguint\":\"42\",\"c\":\"hello\",\"d\":\"0x1\",\"e\":{\"a\":\"42\",\"b\":true,\"c\":\"010203\"},\"f\":null,\"move\":\"move\"}", 9); } }