Documentation
/*
==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--

SJ

Copyright (C) 2019-2025  Anonymous

There are several releases over multiple years,
they are listed as ranges, such as: "2019-2025".

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
*/

//! # Json

mod array_indexes;
mod formatter;
mod impls;
mod object_indexes;

use {
    alloc::{
        string::String,
        vec::Vec,
    },
    crate::{Map, MapKind, Number},
};

#[cfg(feature="std")]
use {
    std::io::Write,
    crate::IoResult,
    self::formatter::*,
};

pub use self::{
    array_indexes::*,
    object_indexes::*,
};

/// # Array
///
/// ## Shortcuts
///
/// <small>[`array()`], [`array_with_capacity()`], [`push()`]</small>
///
/// [`array()`]: fn.array.html
/// [`array_with_capacity()`]: fn.array_with_capacity.html
/// [`push()`]: fn.push.html
pub type Array = Vec<Json>;

/// # Object key
pub type ObjectKey = String;

/// # Json
///
/// ## Formatting
///
/// ### Formatting as JSON string
///
/// - To format as compacted JSON string, you can use [`format()`][fn:Json#format] or [`format_as_bytes()`][fn:Json#format_as_bytes].
/// - To format as a nice JSON string, you can use [`format_nicely()`][fn:Json#format_nicely].
/// - Currently the order of keys in input objects will not be preserved. See [`Map`][enum:Map] for more details.
///
/// ### Writing as JSON string to [`Write`][trait:std/io/Write]
///
/// Can be done via [`write()`][fn:Json#write] or [`write_nicely()`][fn:Json#write_nicely].
///
/// ## Converting Rust types to `Json` and vice versa
///
/// There are some implementations:
///
/// ```ignore
/// impl From<...> for Json;
/// impl TryFrom<&Json> for ...;
/// impl TryFrom<Json> for ...;
/// ```
///
/// About [`TryFrom`][trait:core/convert/TryFrom] implementations:
///
/// - For primitives, since they're cheap, they have implementations on either a borrowed or an owned value.
/// - For collections such as [`String`][enum-variant:Json#String], [`Object`][enum-variant:Json#Object],
///   [`Array`][enum-variant:Json#Array]..., they only have implementations on an owned value. So data is moved, not
///   copied.
///
/// ## Shortcuts
///
/// A root JSON value can be either an object or an array. For your convenience, there are some shortcuts, like below examples.
///
/// - Object:
///
///     ```
///     # #[cfg(feature="std")]
///     # fn test() -> sj::IoResult<()> {
///     use sj::MapKind;
///
///     let mut object = sj::object(MapKind::HashMap);
///     object.insert("first", true)?;
///     object.insert("second", <Option<u8>>::None)?;
///     object.insert(String::from("third"), "...")?;
///
///     assert!(bool::try_from(object.by("first")?)?);
///     assert!(object.take_by("second")?.map_or(true)?);
///     assert!(
///         [r#"{"first":true,"third":"..."}"#, r#"{"third":"...","first":true}"#]
///             .contains(&object.format()?.as_str())
///     );
///     # Ok(()) }
///     # #[cfg(feature="std")]
///     # test().unwrap();
///     # Ok::<_, sj::Error>(())
///     ```
///
/// - Array:
///
///     ```
///     # #[cfg(feature="std")]
///     # fn test() -> sj::IoResult<()> {
///     use sj::MapKind;
///
///     let mut array = sj::array();
///     array.push(false)?;
///     array.push("a string")?;
///     array.push(Some(sj::object(MapKind::BTreeMap)))?;
///
///     assert!(bool::try_from(array.at(0)?)? == false);
///     assert_eq!(array.format()?, r#"[false,"a string",{}]"#);
///     # Ok(()) }
///     # #[cfg(feature="std")]
///     # test().unwrap();
///     # Ok::<_, sj::Error>(())
///     ```
///
/// [trait:core/convert/TryFrom]: https://doc.rust-lang.org/core/convert/trait.TryFrom.html
/// [trait:std/io/Write]: https://doc.rust-lang.org/std/io/trait.Write.html
///
/// [enum:Map]: enum.Map.html
/// [enum-variant:Json#Array]: #variant.Array
/// [enum-variant:Json#Object]: #variant.Object
/// [enum-variant:Json#String]: #variant.String
/// [fn:Json#format]: #method.format
/// [fn:Json#format_nicely]: #method.format_nicely
/// [fn:Json#format_as_bytes]: #method.format_as_bytes
/// [fn:Json#write]: #method.write
/// [fn:Json#write_nicely]: #method.write_nicely
#[derive(Debug, Clone)]
pub enum Json {

    /// [_Shortcuts_](#shortcuts-for-string)
    String(String),

    /// ### Shortcuts
    ///
    /// <small>[`TryFrom`][trait:core/convert/TryFrom]</small>
    ///
    /// [trait:core/convert/TryFrom]: https://doc.rust-lang.org/core/convert/trait.TryFrom.html
    Number(Number),

    /// ### Shortcuts
    ///
    /// <small>[`TryFrom`][trait:core/convert/TryFrom]</small>
    ///
    /// [trait:core/convert/TryFrom]: https://doc.rust-lang.org/core/convert/trait.TryFrom.html
    Boolean(bool),

    /// [_Shortcuts_](#shortcuts-for-null)
    Null,

    /// [_Shortcuts_](#shortcuts-for-object)
    Object(Map),

    /// [_Shortcuts_](#shortcuts-for-array)
    Array(Array),

}

impl Json {

    /// # Formats this value as a compacted JSON string
    #[cfg(feature="std")]
    #[doc(cfg(feature="std"))]
    pub fn format_as_bytes(&self) -> IoResult<Vec<u8>> {
        let mut buf = Vec::with_capacity(formatter::estimate_format_size(self, None, None));
        self.write(&mut buf)?;
        buf.flush().map(|()| buf)
    }

    /// # Nicely formats this value as JSON string
    ///
    /// If you don't provide tab size, default (`4`) will be used.
    #[cfg(feature="std")]
    #[doc(cfg(feature="std"))]
    pub fn format_nicely_as_bytes(&self, tab: Option<u8>) -> IoResult<Vec<u8>> {
        let mut buf = Vec::with_capacity(formatter::estimate_format_size(self, Some(tab.unwrap_or(formatter::DEFAULT_TAB_WIDTH)), None));
        self.write_nicely(tab, &mut buf)?;
        buf.flush().map(|()| buf)
    }

    /// # Formats this value as a compacted JSON string
    #[cfg(feature="std")]
    #[doc(cfg(feature="std"))]
    pub fn format(&self) -> IoResult<String> {
        #[allow(unsafe_code)]
        Ok(unsafe {
            String::from_utf8_unchecked(self.format_as_bytes()?)
        })
    }

    /// # Nicely formats this value as JSON string
    ///
    /// If you don't provide tab size, default (`4`) will be used.
    #[cfg(feature="std")]
    #[doc(cfg(feature="std"))]
    pub fn format_nicely(&self, tab: Option<u8>) -> IoResult<String> {
        #[allow(unsafe_code)]
        Ok(unsafe {
            String::from_utf8_unchecked(self.format_nicely_as_bytes(tab)?)
        })
    }

    /// # Writes this value as compacted JSON string to a stream
    ///
    /// ## Notes
    ///
    /// - The stream is used as-is. You might want to consider using [`BufWriter`][std::io/BufWriter].
    /// - This function does **not** flush the stream when done.
    ///
    /// [std::io/BufWriter]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
    #[cfg(feature="std")]
    #[doc(cfg(feature="std"))]
    pub fn write<W>(&self, stream: &mut W) -> IoResult<()> where W: Write {
        Formatter::new(None).format(self, stream)
    }

    /// # Writes this value as nicely formatted JSON string to a stream
    ///
    /// ## Notes
    ///
    /// - If you don't provide tab size, default (`4`) will be used.
    /// - The stream is used as-is. You might want to consider using [`BufWriter`][std::io/BufWriter].
    /// - This function does **not** flush the stream when done.
    ///
    /// [std::io/BufWriter]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
    #[cfg(feature="std")]
    #[doc(cfg(feature="std"))]
    pub fn write_nicely<W>(&self, tab: Option<u8>, stream: &mut W) -> IoResult<()> where W: Write {
        let tab = match tab {
            Some(_) => tab,
            None => Some(formatter::DEFAULT_TAB_WIDTH),
        };
        Formatter::new(tab).format(self, stream)
    }

}

impl Default for Json {

    fn default() -> Self {
        Self::Null
    }

}

/// # Makes new object
pub fn object(kind: MapKind) -> Json {
    Json::Object(Map::new(kind))
}

/// # Makes new object with capacity
pub fn object_with_capacity(kind: MapKind, capacity: usize) -> Json {
    Json::Object(Map::with_capacity(kind, capacity))
}

/// # Makes new array
pub fn array() -> Json {
    Json::Array(Vec::new())
}

/// # Makes new array with capacity
pub fn array_with_capacity(capacity: usize) -> Json {
    Json::Array(Vec::with_capacity(capacity))
}

/// # Pushes new item into an array
pub fn push<T>(array: &mut Array, json: T) where T: Into<Json> {
    array.push(json.into());
}