use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Number(String);
impl Number {
pub(crate) fn from_normalized(text: String) -> Self {
Self(text)
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
self.0.parse().ok()
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
self.0.parse().ok()
}
#[must_use]
pub fn as_u64(&self) -> Option<u64> {
self.0.parse().ok()
}
#[cfg(feature = "serde")]
pub(crate) fn classify(&self) -> Option<Classified> {
if let Some(value) = self.as_i64() {
return Some(Classified::I64(value));
}
if let Some(value) = self.as_u64() {
return Some(Classified::U64(value));
}
self.as_f64()
.filter(|value| value.is_finite())
.map(Classified::F64)
}
}
#[cfg(feature = "serde")]
#[derive(Clone, Copy)]
pub(crate) enum Classified {
I64(i64),
U64(u64),
F64(f64),
}
impl fmt::Display for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Vec<(String, Value)>),
}
impl Value {
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
_ => None,
}
}
#[must_use]
pub fn as_number(&self) -> Option<&Number> {
match self {
Value::Number(n) => Some(n),
_ => None,
}
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
self.as_number()?.as_f64()
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
self.as_number()?.as_i64()
}
#[must_use]
pub fn as_u64(&self) -> Option<u64> {
self.as_number()?.as_u64()
}
#[must_use]
pub fn as_array(&self) -> Option<&[Value]> {
match self {
Value::Array(a) => Some(a),
_ => None,
}
}
#[must_use]
pub fn as_object(&self) -> Option<&[(String, Value)]> {
match self {
Value::Object(o) => Some(o),
_ => None,
}
}
#[must_use]
pub fn get(&self, key: &str) -> Option<&Value> {
self.as_object()?
.iter()
.find_map(|(k, v)| (k == key).then_some(v))
}
#[must_use]
pub fn index(&self, index: usize) -> Option<&Value> {
self.as_array()?.get(index)
}
#[must_use]
pub fn pointer(&self, pointer: &str) -> Option<&Value> {
pointer_tokens(pointer)?.try_fold(self, |current, token| match current {
Value::Object(_) => current.get(&token),
Value::Array(items) => items.get(array_index(&token)?),
_ => None,
})
}
#[must_use]
pub fn as_array_mut(&mut self) -> Option<&mut Vec<Value>> {
match self {
Value::Array(a) => Some(a),
_ => None,
}
}
#[must_use]
pub fn as_object_mut(&mut self) -> Option<&mut Vec<(String, Value)>> {
match self {
Value::Object(o) => Some(o),
_ => None,
}
}
#[must_use]
pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
self.as_object_mut()?
.iter_mut()
.find_map(|(k, v)| (k == key).then_some(v))
}
#[must_use]
pub fn pointer_mut(&mut self, pointer: &str) -> Option<&mut Value> {
pointer_tokens(pointer)?.try_fold(self, |current, token| match current {
Value::Object(_) => current.get_mut(&token),
Value::Array(items) => items.get_mut(array_index(&token)?),
_ => None,
})
}
#[must_use]
pub fn take(&mut self) -> Value {
core::mem::replace(self, Value::Null)
}
#[must_use]
pub fn len(&self) -> usize {
match self {
Value::Array(a) => a.len(),
Value::Object(o) => o.len(),
_ => 0,
}
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
#[must_use]
pub fn is_bool(&self) -> bool {
matches!(self, Value::Bool(_))
}
#[must_use]
pub fn is_number(&self) -> bool {
matches!(self, Value::Number(_))
}
#[must_use]
pub fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
#[must_use]
pub fn is_array(&self) -> bool {
matches!(self, Value::Array(_))
}
#[must_use]
pub fn is_object(&self) -> bool {
matches!(self, Value::Object(_))
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn write_to(&self, out: &mut String) {
match self {
Value::Null => out.push_str("null"),
Value::Bool(true) => out.push_str("true"),
Value::Bool(false) => out.push_str("false"),
Value::Number(n) => out.push_str(n.as_str()),
Value::String(s) => write_escaped(out, s),
Value::Array(items) => {
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
item.write_to(out);
}
out.push(']');
}
Value::Object(members) => {
out.push('{');
for (i, (key, value)) in members.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_escaped(out, key);
out.push_str(": ");
value.write_to(out);
}
out.push('}');
}
}
}
#[must_use]
pub fn to_json_string(&self) -> String {
let mut out = String::with_capacity(self.render_len_hint());
self.write_to(&mut out);
out
}
fn render_len_hint(&self) -> usize {
match self {
Value::Null => 4,
Value::Bool(true) => 4,
Value::Bool(false) => 5,
Value::Number(n) => n.as_str().len(),
Value::String(s) => s.len() + 2,
Value::Array(items) => {
2 + items.len().saturating_sub(1) * 2
+ items.iter().map(Value::render_len_hint).sum::<usize>()
}
Value::Object(members) => {
2 + members.len().saturating_sub(1) * 2
+ members
.iter()
.map(|(key, value)| key.len() + 4 + value.render_len_hint())
.sum::<usize>()
}
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut out = String::with_capacity(self.render_len_hint());
self.write_to(&mut out);
f.write_str(&out)
}
}
static NULL: Value = Value::Null;
impl core::ops::Index<&str> for Value {
type Output = Value;
fn index(&self, key: &str) -> &Value {
self.get(key).unwrap_or(&NULL)
}
}
impl core::ops::Index<usize> for Value {
type Output = Value;
fn index(&self, index: usize) -> &Value {
self.index(index).unwrap_or(&NULL)
}
}
impl PartialEq<str> for Value {
fn eq(&self, other: &str) -> bool {
self.as_str() == Some(other)
}
}
impl PartialEq<&str> for Value {
fn eq(&self, other: &&str) -> bool {
self.as_str() == Some(*other)
}
}
impl PartialEq<String> for Value {
fn eq(&self, other: &String) -> bool {
self.as_str() == Some(other.as_str())
}
}
impl PartialEq<Value> for str {
fn eq(&self, other: &Value) -> bool {
other == self
}
}
impl PartialEq<Value> for &str {
fn eq(&self, other: &Value) -> bool {
other == self
}
}
impl PartialEq<Value> for String {
fn eq(&self, other: &Value) -> bool {
other == self
}
}
impl PartialEq<bool> for Value {
fn eq(&self, other: &bool) -> bool {
self.as_bool() == Some(*other)
}
}
impl PartialEq<Value> for bool {
fn eq(&self, other: &Value) -> bool {
other == self
}
}
macro_rules! eq_integer {
($($ty:ty => $wide:ty, $as_wide:ident);* $(;)?) => {$(
impl PartialEq<$ty> for Value {
fn eq(&self, other: &$ty) -> bool {
self.$as_wide() == Some(<$wide>::from(*other))
}
}
impl PartialEq<Value> for $ty {
fn eq(&self, other: &Value) -> bool {
other == self
}
}
)*};
}
eq_integer! {
i8 => i64, as_i64;
i16 => i64, as_i64;
i32 => i64, as_i64;
i64 => i64, as_i64;
u8 => u64, as_u64;
u16 => u64, as_u64;
u32 => u64, as_u64;
u64 => u64, as_u64;
}
impl PartialEq<isize> for Value {
fn eq(&self, other: &isize) -> bool {
i64::try_from(*other).is_ok_and(|wide| self.as_i64() == Some(wide))
}
}
impl PartialEq<usize> for Value {
fn eq(&self, other: &usize) -> bool {
u64::try_from(*other).is_ok_and(|wide| self.as_u64() == Some(wide))
}
}
impl PartialEq<f64> for Value {
fn eq(&self, other: &f64) -> bool {
self.as_f64() == Some(*other)
}
}
impl PartialEq<f32> for Value {
fn eq(&self, other: &f32) -> bool {
self.as_f64() == Some(f64::from(*other))
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Value::Bool(value)
}
}
impl From<&str> for Value {
fn from(value: &str) -> Self {
Value::String(String::from(value))
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Value::String(value)
}
}
impl From<Vec<Value>> for Value {
fn from(items: Vec<Value>) -> Self {
Value::Array(items)
}
}
impl From<Vec<(String, Value)>> for Value {
fn from(members: Vec<(String, Value)>) -> Self {
Value::Object(members)
}
}
impl From<()> for Value {
fn from((): ()) -> Self {
Value::Null
}
}
macro_rules! from_integer {
($($ty:ty),* $(,)?) => {$(
impl From<$ty> for Value {
fn from(value: $ty) -> Self {
Value::Number(Number::from_normalized(alloc::string::ToString::to_string(&value)))
}
}
)*};
}
from_integer!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
impl From<f64> for Value {
fn from(value: f64) -> Self {
if value.is_finite() {
Value::Number(Number::from_normalized(alloc::format!("{value:?}")))
} else {
Value::Null
}
}
}
impl From<f32> for Value {
fn from(value: f32) -> Self {
Value::from(f64::from(value))
}
}
impl<T: Into<Value>> From<Option<T>> for Value {
fn from(value: Option<T>) -> Self {
value.map_or(Value::Null, Into::into)
}
}
impl<T: Into<Value>> FromIterator<T> for Value {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Value::Array(iter.into_iter().map(Into::into).collect())
}
}
impl<K: Into<String>, V: Into<Value>> FromIterator<(K, V)> for Value {
fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
Value::Object(
iter.into_iter()
.map(|(key, value)| (key.into(), value.into()))
.collect(),
)
}
}
fn pointer_tokens(pointer: &str) -> Option<impl Iterator<Item = Cow<'_, str>>> {
if !pointer.is_empty() && !pointer.starts_with('/') {
return None;
}
Some(pointer.split('/').skip(1).map(|raw| {
if raw.contains('~') {
Cow::Owned(raw.replace("~1", "/").replace("~0", "~"))
} else {
Cow::Borrowed(raw)
}
}))
}
fn array_index(token: &str) -> Option<usize> {
if token != "0" && token.starts_with('0') {
return None;
}
if !token.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
token.parse::<usize>().ok()
}
pub(crate) fn write_escaped(out: &mut String, text: &str) {
use crate::swar::{broadcast, less_lanes, load_word, zero_lanes};
out.push('"');
let bytes = text.as_bytes();
let n_quote = broadcast(b'"');
let n_backslash = broadcast(b'\\');
let n_control = broadcast(0x20);
let mut plain_start = 0usize;
let mut i = 0usize;
while i < bytes.len() {
while i + 8 <= bytes.len() {
let word = load_word(bytes, i);
let stop = zero_lanes(word ^ n_quote)
| zero_lanes(word ^ n_backslash)
| less_lanes(word, n_control);
if stop != 0 {
break;
}
i += 8;
}
while i < bytes.len() {
let b = bytes[i];
if b == b'"' || b == b'\\' || b < 0x20 {
break;
}
i += 1;
}
if i >= bytes.len() {
break;
}
if i > plain_start {
out.push_str(&text[plain_start..i]);
}
match bytes[i] {
b'"' => out.push_str("\\\""),
b'\\' => out.push_str("\\\\"),
b'\n' => out.push_str("\\n"),
b'\r' => out.push_str("\\r"),
b'\t' => out.push_str("\\t"),
0x08 => out.push_str("\\b"),
0x0C => out.push_str("\\f"),
b => {
const HEX: &[u8; 16] = b"0123456789abcdef";
out.push_str("\\u00");
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0xF) as usize] as char);
}
}
i += 1;
plain_start = i;
}
if plain_start < bytes.len() {
out.push_str(&text[plain_start..]);
}
out.push('"');
}