Skip to main content

rolldown_common/ecmascript/
json_to_program.rs

1use arcstr::ArcStr;
2use oxc::{
3  allocator::Allocator,
4  ast::AstBuilder,
5  span::{SPAN, SourceType},
6};
7use rolldown_ecmascript::EcmaAst;
8use serde_json::Value;
9
10/// Converts a `serde_json::Value` to an `EcmaAst`.
11///
12/// The resulting AST contains a single expression statement with the JSON value
13/// converted to its JavaScript AST equivalent.
14///
15/// # Arguments
16/// * `value` - The JSON value to convert
17///
18/// # Returns
19/// An `EcmaAst` containing the converted program
20///
21/// # Example
22/// ```ignore
23/// use serde_json::json;
24/// use rolldown_common::json_value_to_ecma_ast;
25///
26/// let value = json!({"name": "test", "count": 42});
27/// let ast = json_value_to_ecma_ast(&value);
28/// ```
29pub fn json_value_to_ecma_ast(value: &Value) -> EcmaAst {
30  let source = ArcStr::from("");
31  let allocator = Allocator::default();
32
33  EcmaAst::from_allocator_and_source(source, allocator, |allocator| {
34    let builder = AstBuilder::new(allocator);
35    let expr = json_value_to_expression(value, builder);
36    let stmt = builder.statement_expression(SPAN, expr);
37
38    builder.program(
39      SPAN,
40      SourceType::default().with_module(true),
41      "",
42      builder.vec(),
43      None,
44      builder.vec(),
45      builder.vec1(stmt),
46    )
47  })
48}
49
50/// Converts a `serde_json::Value` to an oxc `Expression`.
51///
52/// This is useful when you need just the expression without wrapping it in a program.
53///
54/// # Arguments
55/// * `value` - The JSON value to convert
56/// * `builder` - The AST builder to use for node creation
57///
58/// # Returns
59/// An `Expression` representing the JSON value
60pub fn json_value_to_expression<'a>(
61  value: &Value,
62  builder: AstBuilder<'a>,
63) -> oxc::ast::ast::Expression<'a> {
64  match value {
65    Value::Null => builder.expression_null_literal(SPAN),
66
67    Value::Bool(b) => builder.expression_boolean_literal(SPAN, *b),
68
69    Value::Number(n) => {
70      // serde_json::Number can always be represented as f64 for JSON numbers.
71      // Large integers may lose precision, matching JavaScript's JSON.parse behavior.
72      let f = n.as_f64().expect("JSON numbers are always representable as f64");
73      builder.expression_numeric_literal(SPAN, f, None, oxc::ast::ast::NumberBase::Decimal)
74    }
75
76    Value::String(s) => builder.expression_string_literal(SPAN, builder.str(s), None),
77
78    Value::Array(arr) => {
79      let elements = builder.vec_from_iter(arr.iter().map(|item| {
80        let expr = json_value_to_expression(item, builder);
81        oxc::ast::ast::ArrayExpressionElement::from(expr)
82      }));
83      builder.expression_array(SPAN, elements)
84    }
85
86    Value::Object(obj) => {
87      let properties = builder.vec_from_iter(obj.iter().map(|(key, val)| {
88        let key_expr = builder.expression_string_literal(SPAN, builder.str(key), None);
89        let value_expr = json_value_to_expression(val, builder);
90
91        builder.object_property_kind_object_property(
92          SPAN,
93          oxc::ast::ast::PropertyKind::Init,
94          oxc::ast::ast::PropertyKey::from(key_expr),
95          value_expr,
96          false, // shorthand
97          false, // computed
98          false, // method
99        )
100      }));
101      builder.expression_object(SPAN, properties)
102    }
103  }
104}
105
106#[cfg(test)]
107mod tests {
108  use insta::assert_snapshot;
109  use oxc::codegen::Codegen;
110
111  use super::*;
112
113  fn to_code(value: &Value) -> String {
114    let ast = json_value_to_ecma_ast(value);
115    Codegen::new().build(ast.program()).code
116  }
117
118  #[test]
119  fn test_primitives() {
120    assert_snapshot!(to_code(&serde_json::json!(null)), @"null;");
121    assert_snapshot!(to_code(&serde_json::json!(true)), @"true;");
122    assert_snapshot!(to_code(&serde_json::json!(false)), @"false;");
123    assert_snapshot!(to_code(&serde_json::json!(42)), @"42;");
124    assert_snapshot!(to_code(&serde_json::json!(3.5)), @"3.5;");
125    assert_snapshot!(to_code(&serde_json::json!(-17)), @"-17;");
126    assert_snapshot!(to_code(&serde_json::json!(0)), @"0;");
127    assert_snapshot!(to_code(&serde_json::json!("hello")), @r#"("hello");"#);
128    assert_snapshot!(to_code(&serde_json::json!("")), @r#"("");"#);
129    assert_snapshot!(to_code(&serde_json::json!("with \"quotes\"")), @r#"("with \"quotes\"");"#);
130  }
131
132  /// Large integers beyond MAX_SAFE_INTEGER lose precision when parsed as f64.
133  /// This matches JavaScript's `JSON.parse` behavior:
134  /// `JSON.parse('{ "v": 9007199254740995 }').v` returns `9007199254740996`
135  #[test]
136  fn test_large_integer_precision_loss() {
137    // 9007199254740995 is beyond Number.MAX_SAFE_INTEGER (2^53 - 1 = 9007199254740991)
138    // When parsed as f64 and back, it becomes 9007199254740996
139    let json: Value = serde_json::from_str(r#"{ "v": 9007199254740995 }"#).unwrap();
140    assert_snapshot!(to_code(&json), @r#"({ "v": 9007199254740996 });"#);
141  }
142
143  #[test]
144  fn test_array() {
145    assert_snapshot!(to_code(&serde_json::json!([])), @"[];");
146    assert_snapshot!(to_code(&serde_json::json!([1, 2, 3])), @r"
147    [
148    	1,
149    	2,
150    	3
151    ];
152    ");
153    assert_snapshot!(to_code(&serde_json::json!(["a", "b"])), @r#"["a", "b"];"#);
154    assert_snapshot!(to_code(&serde_json::json!([1, "mixed", true, null])), @r#"
155    [
156    	1,
157    	"mixed",
158    	true,
159    	null
160    ];
161    "#);
162  }
163
164  #[test]
165  fn test_object() {
166    assert_snapshot!(to_code(&serde_json::json!({})), @"({});");
167    assert_snapshot!(to_code(&serde_json::json!({"a": 1})), @r#"({ "a": 1 });"#);
168    assert_snapshot!(to_code(&serde_json::json!({"key with spaces": 1})), @r#"({ "key with spaces": 1 });"#);
169    assert_snapshot!(to_code(&serde_json::json!({"true": 1})), @r#"({ "true": 1 });"#);
170    // Note: serde_json deduplicates keys, keeping the last value
171    assert_snapshot!(to_code(&serde_json::from_str::<Value>(r#"{"a": 1, "a": 2}"#).unwrap()), @r#"({ "a": 2 });"#);
172  }
173
174  /// Regression test for https://github.com/vitejs/vite/issues/21982
175  #[test]
176  fn test_float_17_significant_digits() {
177    let inputs = [
178      114.351_437_992_579_97_f64,
179      406.314_867_132_489_95_f64,
180      163.414_980_184_984_98_f64,
181      364.094_987_249_009_9_f64,
182    ];
183    let json: Value = serde_json::from_str(
184      r"[114.35143799257997, 406.31486713248995, 163.41498018498498, 364.09498724900986]",
185    )
186    .unwrap();
187    let code: String = to_code(&json).chars().filter(|c| !c.is_whitespace()).collect();
188    let expected = format!("[{}];", inputs.map(|v| v.to_string()).join(","));
189    assert_eq!(code, expected);
190  }
191
192  #[test]
193  fn test_nested() {
194    assert_snapshot!(to_code(&serde_json::json!({
195      "name": "test",
196      "values": [1, 2, 3],
197      "nested": {
198        "deep": true
199      }
200    })), @r#"
201    ({
202    	"name": "test",
203    	"values": [
204    		1,
205    		2,
206    		3
207    	],
208    	"nested": { "deep": true }
209    });
210    "#);
211  }
212}