sj/json.rs
1/*
2==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--
3
4SJ
5
6Copyright (C) 2019-2025 Anonymous
7
8There are several releases over multiple years,
9they are listed as ranges, such as: "2019-2025".
10
11This program is free software: you can redistribute it and/or modify
12it under the terms of the GNU Lesser General Public License as published by
13the Free Software Foundation, either version 3 of the License, or
14(at your option) any later version.
15
16This program is distributed in the hope that it will be useful,
17but WITHOUT ANY WARRANTY; without even the implied warranty of
18MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19GNU Lesser General Public License for more details.
20
21You should have received a copy of the GNU Lesser General Public License
22along with this program. If not, see <https://www.gnu.org/licenses/>.
23
24::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
25*/
26
27//! # Json
28
29mod array_indexes;
30mod formatter;
31mod impls;
32mod object_indexes;
33
34use {
35 alloc::{
36 string::String,
37 vec::Vec,
38 },
39 crate::{Map, MapKind, Number},
40};
41
42#[cfg(feature="std")]
43use {
44 std::io::Write,
45 crate::IoResult,
46 self::formatter::*,
47};
48
49pub use self::{
50 array_indexes::*,
51 object_indexes::*,
52};
53
54/// # Array
55///
56/// ## Shortcuts
57///
58/// <small>[`array()`], [`array_with_capacity()`], [`push()`]</small>
59///
60/// [`array()`]: fn.array.html
61/// [`array_with_capacity()`]: fn.array_with_capacity.html
62/// [`push()`]: fn.push.html
63pub type Array = Vec<Json>;
64
65/// # Object key
66pub type ObjectKey = String;
67
68/// # Json
69///
70/// ## Formatting
71///
72/// ### Formatting as JSON string
73///
74/// - To format as compacted JSON string, you can use [`format()`][fn:Json#format] or [`format_as_bytes()`][fn:Json#format_as_bytes].
75/// - To format as a nice JSON string, you can use [`format_nicely()`][fn:Json#format_nicely].
76/// - Currently the order of keys in input objects will not be preserved. See [`Map`][enum:Map] for more details.
77///
78/// ### Writing as JSON string to [`Write`][trait:std/io/Write]
79///
80/// Can be done via [`write()`][fn:Json#write] or [`write_nicely()`][fn:Json#write_nicely].
81///
82/// ## Converting Rust types to `Json` and vice versa
83///
84/// There are some implementations:
85///
86/// ```ignore
87/// impl From<...> for Json;
88/// impl TryFrom<&Json> for ...;
89/// impl TryFrom<Json> for ...;
90/// ```
91///
92/// About [`TryFrom`][trait:core/convert/TryFrom] implementations:
93///
94/// - For primitives, since they're cheap, they have implementations on either a borrowed or an owned value.
95/// - For collections such as [`String`][enum-variant:Json#String], [`Object`][enum-variant:Json#Object],
96/// [`Array`][enum-variant:Json#Array]..., they only have implementations on an owned value. So data is moved, not
97/// copied.
98///
99/// ## Shortcuts
100///
101/// A root JSON value can be either an object or an array. For your convenience, there are some shortcuts, like below examples.
102///
103/// - Object:
104///
105/// ```
106/// # #[cfg(feature="std")]
107/// # fn test() -> sj::IoResult<()> {
108/// use sj::MapKind;
109///
110/// let mut object = sj::object(MapKind::HashMap);
111/// object.insert("first", true)?;
112/// object.insert("second", <Option<u8>>::None)?;
113/// object.insert(String::from("third"), "...")?;
114///
115/// assert!(bool::try_from(object.by("first")?)?);
116/// assert!(object.take_by("second")?.map_or(true)?);
117/// assert!(
118/// [r#"{"first":true,"third":"..."}"#, r#"{"third":"...","first":true}"#]
119/// .contains(&object.format()?.as_str())
120/// );
121/// # Ok(()) }
122/// # #[cfg(feature="std")]
123/// # test().unwrap();
124/// # Ok::<_, sj::Error>(())
125/// ```
126///
127/// - Array:
128///
129/// ```
130/// # #[cfg(feature="std")]
131/// # fn test() -> sj::IoResult<()> {
132/// use sj::MapKind;
133///
134/// let mut array = sj::array();
135/// array.push(false)?;
136/// array.push("a string")?;
137/// array.push(Some(sj::object(MapKind::BTreeMap)))?;
138///
139/// assert!(bool::try_from(array.at(0)?)? == false);
140/// assert_eq!(array.format()?, r#"[false,"a string",{}]"#);
141/// # Ok(()) }
142/// # #[cfg(feature="std")]
143/// # test().unwrap();
144/// # Ok::<_, sj::Error>(())
145/// ```
146///
147/// [trait:core/convert/TryFrom]: https://doc.rust-lang.org/core/convert/trait.TryFrom.html
148/// [trait:std/io/Write]: https://doc.rust-lang.org/std/io/trait.Write.html
149///
150/// [enum:Map]: enum.Map.html
151/// [enum-variant:Json#Array]: #variant.Array
152/// [enum-variant:Json#Object]: #variant.Object
153/// [enum-variant:Json#String]: #variant.String
154/// [fn:Json#format]: #method.format
155/// [fn:Json#format_nicely]: #method.format_nicely
156/// [fn:Json#format_as_bytes]: #method.format_as_bytes
157/// [fn:Json#write]: #method.write
158/// [fn:Json#write_nicely]: #method.write_nicely
159#[derive(Debug, Clone)]
160pub enum Json {
161
162 /// [_Shortcuts_](#shortcuts-for-string)
163 String(String),
164
165 /// ### Shortcuts
166 ///
167 /// <small>[`TryFrom`][trait:core/convert/TryFrom]</small>
168 ///
169 /// [trait:core/convert/TryFrom]: https://doc.rust-lang.org/core/convert/trait.TryFrom.html
170 Number(Number),
171
172 /// ### Shortcuts
173 ///
174 /// <small>[`TryFrom`][trait:core/convert/TryFrom]</small>
175 ///
176 /// [trait:core/convert/TryFrom]: https://doc.rust-lang.org/core/convert/trait.TryFrom.html
177 Boolean(bool),
178
179 /// [_Shortcuts_](#shortcuts-for-null)
180 Null,
181
182 /// [_Shortcuts_](#shortcuts-for-object)
183 Object(Map),
184
185 /// [_Shortcuts_](#shortcuts-for-array)
186 Array(Array),
187
188}
189
190impl Json {
191
192 /// # Formats this value as a compacted JSON string
193 #[cfg(feature="std")]
194 #[doc(cfg(feature="std"))]
195 pub fn format_as_bytes(&self) -> IoResult<Vec<u8>> {
196 let mut buf = Vec::with_capacity(formatter::estimate_format_size(self, None, None));
197 self.write(&mut buf)?;
198 buf.flush().map(|()| buf)
199 }
200
201 /// # Nicely formats this value as JSON string
202 ///
203 /// If you don't provide tab size, default (`4`) will be used.
204 #[cfg(feature="std")]
205 #[doc(cfg(feature="std"))]
206 pub fn format_nicely_as_bytes(&self, tab: Option<u8>) -> IoResult<Vec<u8>> {
207 let mut buf = Vec::with_capacity(formatter::estimate_format_size(self, Some(tab.unwrap_or(formatter::DEFAULT_TAB_WIDTH)), None));
208 self.write_nicely(tab, &mut buf)?;
209 buf.flush().map(|()| buf)
210 }
211
212 /// # Formats this value as a compacted JSON string
213 #[cfg(feature="std")]
214 #[doc(cfg(feature="std"))]
215 pub fn format(&self) -> IoResult<String> {
216 #[allow(unsafe_code)]
217 Ok(unsafe {
218 String::from_utf8_unchecked(self.format_as_bytes()?)
219 })
220 }
221
222 /// # Nicely formats this value as JSON string
223 ///
224 /// If you don't provide tab size, default (`4`) will be used.
225 #[cfg(feature="std")]
226 #[doc(cfg(feature="std"))]
227 pub fn format_nicely(&self, tab: Option<u8>) -> IoResult<String> {
228 #[allow(unsafe_code)]
229 Ok(unsafe {
230 String::from_utf8_unchecked(self.format_nicely_as_bytes(tab)?)
231 })
232 }
233
234 /// # Writes this value as compacted JSON string to a stream
235 ///
236 /// ## Notes
237 ///
238 /// - The stream is used as-is. You might want to consider using [`BufWriter`][std::io/BufWriter].
239 /// - This function does **not** flush the stream when done.
240 ///
241 /// [std::io/BufWriter]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
242 #[cfg(feature="std")]
243 #[doc(cfg(feature="std"))]
244 pub fn write<W>(&self, stream: &mut W) -> IoResult<()> where W: Write {
245 Formatter::new(None).format(self, stream)
246 }
247
248 /// # Writes this value as nicely formatted JSON string to a stream
249 ///
250 /// ## Notes
251 ///
252 /// - If you don't provide tab size, default (`4`) will be used.
253 /// - The stream is used as-is. You might want to consider using [`BufWriter`][std::io/BufWriter].
254 /// - This function does **not** flush the stream when done.
255 ///
256 /// [std::io/BufWriter]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
257 #[cfg(feature="std")]
258 #[doc(cfg(feature="std"))]
259 pub fn write_nicely<W>(&self, tab: Option<u8>, stream: &mut W) -> IoResult<()> where W: Write {
260 let tab = match tab {
261 Some(_) => tab,
262 None => Some(formatter::DEFAULT_TAB_WIDTH),
263 };
264 Formatter::new(tab).format(self, stream)
265 }
266
267}
268
269impl Default for Json {
270
271 fn default() -> Self {
272 Self::Null
273 }
274
275}
276
277/// # Makes new object
278pub fn object(kind: MapKind) -> Json {
279 Json::Object(Map::new(kind))
280}
281
282/// # Makes new object with capacity
283pub fn object_with_capacity(kind: MapKind, capacity: usize) -> Json {
284 Json::Object(Map::with_capacity(kind, capacity))
285}
286
287/// # Makes new array
288pub fn array() -> Json {
289 Json::Array(Vec::new())
290}
291
292/// # Makes new array with capacity
293pub fn array_with_capacity(capacity: usize) -> Json {
294 Json::Array(Vec::with_capacity(capacity))
295}
296
297/// # Pushes new item into an array
298pub fn push<T>(array: &mut Array, json: T) where T: Into<Json> {
299 array.push(json.into());
300}