json2lua/
lib.rs

1//         _                 ___   __
2//        (_)________  ____ |__ \ / /_  __ _____
3//       / / ___/ __ \/ __ \__/ // / / / / __  /
4//      / (__  / /_/ / / / / __// / /_/ / /_/ /
5//   __/ /____/\____/_/ /_/____/_/\____/\____/
6//  /___/
7//
8//! # json2lua
9//!
10//! Convert JSON to Lua table
11//!
12//! ## Example:
13//! ```rust
14//! use json2lua::parse;
15//!
16//! let json = r#"{
17//!   "string": "json2lua",
18//!   "int": 420,
19//!   "bool": true,
20//!   "null": null
21//! }"#;
22//!
23//! let lua = parse(json).unwrap();
24//! // Output:
25//! // {
26//! //   ["string"] = "json2lua",
27//! //   ["int"] = 420,
28//! //   ["bool"] = true,
29//! //   ["null"] = nil,
30//! // }
31//! ```
32//!
33//! Made with <3 by Dervex
34
35#![allow(clippy::tabs_in_doc_comments)]
36
37use indexmap::IndexMap;
38use serde::Deserialize;
39use serde_json::{from_str, Result, Value};
40
41#[derive(Deserialize)]
42#[serde(untagged)]
43enum Json {
44	Sequence(Vec<Value>),
45	Map(IndexMap<String, Value>),
46}
47
48/// Parse JSON string into a Lua table
49///
50/// ```rust
51/// use json2lua::parse;
52///
53/// let json = r#"{
54/// 	"string": "abc",
55/// 	"int": 123,
56/// 	"bool": true,
57/// 	"null": null
58/// }"#;
59///
60/// let lua = r#"{
61/// 	["string"] = "abc",
62/// 	["int"] = 123,
63/// 	["bool"] = true,
64/// 	["null"] = nil,
65/// }"#;
66///
67/// assert_eq!(parse(json).unwrap(), lua);
68/// ```
69pub fn parse(json: &str) -> Result<String> {
70	let mut lua = String::from("{\n");
71
72	match from_str(json)? {
73		Json::Sequence(json) => {
74			for value in json {
75				lua.push_str(&walk(None, &value, 1));
76			}
77		}
78		Json::Map(json) => {
79			for (key, value) in json {
80				lua.push_str(&walk(Some(&validate_string(&key)), &value, 1));
81			}
82		}
83	}
84
85	lua.push('}');
86
87	Ok(lua)
88}
89
90fn walk(key: Option<&str>, value: &Value, depth: usize) -> String {
91	let mut lua = String::new();
92
93	lua.push_str(&get_indent(depth));
94
95	if let Some(key) = key {
96		lua.push_str(&format!("[\"{}\"] = ", validate_string(key)));
97	}
98
99	match value {
100		Value::String(s) => lua.push_str(&format!("\"{}\"", &validate_string(s))),
101		Value::Number(n) => lua.push_str(&n.to_string()),
102		Value::Bool(b) => lua.push_str(&b.to_string()),
103		Value::Null => lua.push_str("nil"),
104		Value::Array(a) => {
105			lua.push_str("{\n");
106
107			for v in a {
108				lua.push_str(&walk(None, v, depth + 1));
109			}
110
111			lua.push_str(&get_indent(depth));
112			lua.push('}');
113		}
114		Value::Object(o) => {
115			lua.push_str("{\n");
116
117			for (k, v) in o {
118				lua.push_str(&walk(Some(k), v, depth + 1));
119			}
120
121			lua.push_str(&get_indent(depth));
122			lua.push('}');
123		}
124	}
125
126	lua.push_str(",\n");
127
128	lua
129}
130
131fn get_indent(depth: usize) -> String {
132	let mut indent = String::new();
133
134	for _ in 0..depth {
135		indent.push('\t');
136	}
137
138	indent
139}
140
141fn validate_string(string: &str) -> String {
142	let mut validated = String::new();
143
144	for char in string.chars() {
145		match char {
146			'\n' => validated.push_str("\\n"),
147			'\t' => validated.push_str("\\t"),
148			'\r' => validated.push_str("\\r"),
149			'\\' => validated.push_str("\\\\"),
150			'"' => validated.push_str("\\\""),
151			_ => validated.push(char),
152		}
153	}
154
155	validated
156}
157
158#[cfg(test)]
159mod test {
160	#[test]
161	fn all_values() {
162		use crate::parse;
163
164		let json = r#"{
165  "string": "str",
166  "int": 420,
167  "float": 4.2,
168  "bool": true,
169  "null": null,
170  "array": [
171    "string",
172    12345,
173    false,
174    {
175      "k": "v"
176    }
177  ],
178  "object": {
179    "key": "value"
180  }
181}"#;
182
183		let lua = r#"{
184	["string"] = "str",
185	["int"] = 420,
186	["float"] = 4.2,
187	["bool"] = true,
188	["null"] = nil,
189	["array"] = {
190		"string",
191		12345,
192		false,
193		{
194			["k"] = "v",
195		},
196	},
197	["object"] = {
198		["key"] = "value",
199	},
200}"#;
201
202		assert_eq!(parse(json).unwrap(), lua);
203	}
204
205	#[test]
206	fn malformed_strings() {
207		use crate::parse;
208
209		let json = r#"{
210  "1": "..\n..",
211  "2": "..\t..",
212  "3": "..\r..",
213  "4": "..\\..",
214  "5": "..\".."
215}"#;
216
217		let lua = r#"{
218	["1"] = "..\n..",
219	["2"] = "..\t..",
220	["3"] = "..\r..",
221	["4"] = "..\\..",
222	["5"] = "..\"..",
223}"#;
224
225		assert_eq!(parse(json).unwrap(), lua);
226	}
227
228	#[test]
229	fn root_array() {
230		use crate::parse;
231
232		let json = r#"["a", "b", "c"]"#;
233
234		let lua = r#"{
235	"a",
236	"b",
237	"c",
238}"#;
239
240		assert_eq!(parse(json).unwrap(), lua);
241	}
242}