From 1bca3a7f63cf05831fe523222cbbb1139af09bf7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 14 Dec 2023 02:19:41 +0800 Subject: [PATCH] Fix format json bug. --- CHANGELOG.md | 6 +++++ src/api/json.rs | 55 ++++++++++++++++++++++++++++++++++++------ src/ast/stmt.rs | 2 +- src/serde/serialize.rs | 4 +++ tests/maps.rs | 4 +++ 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6b40a38f..a8b0311eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,12 @@ Fixes to bugs found via fuzzing * Indexing into an array with a negative index that is larger than the length of the array now throws an out-of-bounds error (similar to positive indices) instead of defaulting to the first element. * Fixed edge-case crash in timestamp functions. +Other bug fixes +--------------- + +* Arrays in object maps now serialize to JSON correctly via `to_json()` when the `serde` feature is not enabled. +* `Engine::format_map_as_json` now serializes arrays correctly. + Enhancements ------------ diff --git a/src/api/json.rs b/src/api/json.rs index 378fbe57c..72e92ea3f 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -4,8 +4,10 @@ use crate::func::native::locked_write; use crate::parser::{ParseSettingFlags, ParseState}; use crate::tokenizer::{lex_raw, Token}; +use crate::types::dynamic::Union; use crate::types::StringsInterner; -use crate::{Engine, LexError, Map, RhaiResultOf}; +use crate::{Dynamic, Engine, LexError, Map, RhaiResultOf}; +use std::fmt::Write; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -166,8 +168,6 @@ pub fn format_map_as_json(map: &Map) -> String { let mut result = String::from('{'); for (key, value) in map { - use std::fmt::Write; - if result.len() > 1 { result.push(','); } @@ -175,14 +175,53 @@ pub fn format_map_as_json(map: &Map) -> String { write!(result, "{key:?}").unwrap(); result.push(':'); - match value.read_lock::() { - Some(val) => result.push_str(&format_map_as_json(&val)), - None if value.is_unit() => result.push_str("null"), - None => write!(result, "{value:?}").unwrap(), - } + format_dynamic_as_json(&mut result, value); } result.push('}'); result } + +/// Format a [`Dynamic`] value as JSON. +fn format_dynamic_as_json(result: &mut String, value: &Dynamic) { + match value.0 { + Union::Unit(..) => result.push_str("null"), + Union::FnPtr(ref f, _, _) if f.is_curried() => { + result.push('['); + write!(result, "{:?}", f.fn_name()).unwrap(); + f.iter_curry().for_each(|value| { + result.push(','); + format_dynamic_as_json(result, value); + }); + result.push(']'); + } + Union::FnPtr(ref f, _, _) => write!(result, "{:?}", f.fn_name()).unwrap(), + Union::Map(ref m, ..) => result.push_str(&format_map_as_json(&m)), + #[cfg(not(feature = "no_index"))] + Union::Array(ref a, _, _) => { + result.push('['); + for (i, x) in a.iter().enumerate() { + if i > 0 { + result.push(','); + } + format_dynamic_as_json(result, x); + } + result.push(']'); + } + #[cfg(not(feature = "no_index"))] + Union::Blob(ref b, _, _) => { + result.push('['); + for (i, x) in b.iter().enumerate() { + if i > 0 { + result.push(','); + } + write!(result, "{x}").unwrap(); + } + result.push(']'); + } + #[cfg(not(feature = "no_closure"))] + Union::Shared(ref v, _, _) => format_dynamic_as_json(result, &*v.borrow()), + _ => write!(result, "{value:?}").unwrap(), + } +} diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index c73ee5493..358fe5554 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -175,7 +175,7 @@ impl fmt::Debug for OpAssignment { .field("pos", &self.pos) .finish() } else { - fmt::Debug::fmt(&self.pos, f) + write!(f, "{} @ {:?}", Token::Equals, self.pos) } } } diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index ec3d4c27c..252e3243d 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -3,6 +3,7 @@ use crate::types::dynamic::Union; use crate::{Dynamic, ImmutableString, Scope}; use serde::{ser::SerializeSeq, Serialize, Serializer}; +use std::iter::once; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -63,6 +64,9 @@ impl Serialize for Dynamic { m.iter().try_for_each(|(k, v)| map.serialize_entry(k, v))?; map.end() } + Union::FnPtr(ref f, ..) if f.is_curried() => { + ser.collect_seq(once(f.fn_name().into()).chain(f.iter_curry().cloned())) + } Union::FnPtr(ref f, ..) => ser.serialize_str(f.fn_name()), #[cfg(not(feature = "no_time"))] Union::TimeStamp(ref x, ..) => ser.serialize_str(x.as_ref().type_name()), diff --git a/tests/maps.rs b/tests/maps.rs index f55a46cf9..23e4f7a85 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -218,6 +218,10 @@ fn test_map_json() { .len(), 11 ); + + assert_eq!(engine.eval::("#{a:[#{b:42}]}.to_json()").unwrap(), r#"{"a":[{"b":42}]}"#); + assert_eq!(engine.eval::(r#"#{a:[Fn("abc")]}.to_json()"#).unwrap(), r#"{"a":["abc"]}"#); + assert_eq!(engine.eval::(r#"#{a:[Fn("abc").curry(42).curry(123)]}.to_json()"#).unwrap(), r#"{"a":[["abc",42,123]]}"#); } engine.parse_json(json, true).unwrap();