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}