use super::lazy;
use miniserde::json::{Array, Number, Object, Value};
use std::rc::Rc;
#[derive(Clone)]
#[allow(clippy::redundant_pub_crate)]
pub(crate) enum JsonInner {
Lazy { bytes: Rc<[u8]> },
Parsed(Rc<Value>),
}
#[derive(Clone)]
pub struct JsonValue {
pub(crate) inner: JsonInner,
}
impl JsonValue {
pub(crate) fn new(v: Value) -> Self {
Self {
inner: JsonInner::Parsed(Rc::new(v)),
}
}
pub(crate) fn from_bytes(bytes: &[u8]) -> Self {
Self {
inner: JsonInner::Lazy {
bytes: Rc::from(bytes),
},
}
}
pub(crate) fn null() -> Self {
Self::new(Value::Null)
}
pub(crate) fn bytes(&self) -> Option<&[u8]> {
match &self.inner {
JsonInner::Lazy { bytes } => Some(bytes),
JsonInner::Parsed(_) => None,
}
}
pub(crate) fn parse_bytes(bytes: &[u8]) -> Option<Value> {
let s = std::str::from_utf8(bytes).ok()?;
miniserde::json::from_str(s).ok()
}
pub(crate) fn value(&self) -> &Value {
static NULL: Value = Value::Null;
match &self.inner {
JsonInner::Parsed(v) => v,
JsonInner::Lazy { .. } => &NULL,
}
}
fn get_value_for_tree(&self) -> Value {
match &self.inner {
JsonInner::Parsed(v) => (**v).clone(),
JsonInner::Lazy { bytes } => Self::parse_bytes(bytes).unwrap_or(Value::Null),
}
}
#[must_use]
pub fn get(&self, key: &str) -> Self {
match self.get_value_for_tree() {
Value::Object(obj) => obj.get(key).cloned().map_or_else(Self::null, Self::new),
_ => Self::null(),
}
}
#[must_use]
pub fn at(&self, index: usize) -> Self {
match self.get_value_for_tree() {
Value::Array(arr) => arr.get(index).cloned().map_or_else(Self::null, Self::new),
_ => Self::null(),
}
}
#[must_use]
pub fn map_array<T, F>(&self, f: F) -> Option<Vec<T>>
where
F: Fn(&Value) -> Option<T>,
{
match self.get_value_for_tree() {
Value::Array(arr) => {
let mut result = Vec::with_capacity(arr.len());
for elem in &arr {
result.push(f(elem)?);
}
Some(result)
},
_ => None,
}
}
#[must_use]
pub fn try_map_array<T, E, F>(&self, f: F) -> Option<Result<Vec<T>, E>>
where
F: Fn(&Value) -> Result<T, E>,
{
match self.get_value_for_tree() {
Value::Array(arr) => {
let mut result = Vec::with_capacity(arr.len());
for elem in &arr {
match f(elem) {
Ok(v) => result.push(v),
Err(e) => return Some(Err(e)),
}
}
Some(Ok(result))
},
_ => None,
}
}
#[must_use]
pub fn from_raw(value: &Value) -> Self {
Self::new(value.clone())
}
#[must_use]
pub fn str(&self) -> Option<String> {
match self.get_value_for_tree() {
Value::String(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn str_or(&self, default: &str) -> String {
self.str().unwrap_or_else(|| default.to_string())
}
#[must_use]
pub fn int(&self) -> Option<i64> {
match self.get_value_for_tree() {
Value::Number(n) => match n {
Number::I64(i) => Some(i),
Number::U64(u) => u.try_into().ok(),
Number::F64(f) => {
const MAX_SAFE_INT: f64 = 9007199254740992.0; if f.is_finite() && f.abs() <= MAX_SAFE_INT {
Some(f as i64)
} else {
None
}
},
},
_ => None,
}
}
#[must_use]
pub fn int_or(&self, default: i64) -> i64 {
self.int().unwrap_or(default)
}
#[must_use]
#[allow(clippy::cast_precision_loss)] pub fn float(&self) -> Option<f64> {
match self.get_value_for_tree() {
Value::Number(n) => match n {
Number::F64(f) if f.is_finite() => Some(f),
Number::I64(i) => Some(i as f64),
Number::U64(u) => Some(u as f64),
Number::F64(_) => None, },
_ => None,
}
}
#[must_use]
pub fn float_or(&self, default: f64) -> f64 {
self.float().unwrap_or(default)
}
#[must_use]
pub fn bool(&self) -> Option<bool> {
match self.get_value_for_tree() {
Value::Bool(b) => Some(b),
_ => None,
}
}
#[must_use]
pub fn bool_or(&self, default: bool) -> bool {
self.bool().unwrap_or(default)
}
#[must_use]
pub fn is_null(&self) -> bool {
matches!(self.get_value_for_tree(), Value::Null)
}
#[must_use]
pub fn keys(&self) -> Vec<String> {
match self.get_value_for_tree() {
Value::Object(obj) => obj.keys().cloned().collect(),
_ => Vec::new(),
}
}
#[must_use]
pub fn len(&self) -> Option<usize> {
match self.get_value_for_tree() {
Value::Array(arr) => Some(arr.len()),
Value::Object(obj) => Some(obj.len()),
_ => None,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len().is_some_and(|l| l == 0)
}
fn get_path(&self, path: &[&str]) -> Option<&Value> {
let mut current = self.value();
for key in path {
match current {
Value::Object(obj) => {
current = obj.get(*key)?;
},
_ => return None,
}
}
Some(current)
}
#[must_use]
pub fn path_str(&self, path: &[&str]) -> Option<String> {
if let Some(bytes) = self.bytes() {
return lazy::path_str(bytes, path);
}
match self.get_path(path)? {
Value::String(s) => Some(s.clone()),
_ => None,
}
}
#[must_use]
pub fn path_str_or(&self, path: &[&str], default: &str) -> String {
self.path_str(path).unwrap_or_else(|| default.to_string())
}
#[must_use]
pub fn path_int(&self, path: &[&str]) -> Option<i64> {
if let Some(bytes) = self.bytes() {
return lazy::path_int(bytes, path);
}
match self.get_path(path)? {
Value::Number(n) => match n {
Number::I64(i) => Some(*i),
Number::U64(u) => (*u).try_into().ok(),
Number::F64(f) => {
const MAX_SAFE_INT: f64 = 9007199254740992.0;
if f.is_finite() && f.abs() <= MAX_SAFE_INT {
Some(*f as i64)
} else {
None
}
},
},
_ => None,
}
}
#[must_use]
pub fn path_int_or(&self, path: &[&str], default: i64) -> i64 {
self.path_int(path).unwrap_or(default)
}
#[must_use]
#[allow(clippy::cast_precision_loss)] pub fn path_float(&self, path: &[&str]) -> Option<f64> {
if let Some(bytes) = self.bytes() {
return lazy::path_float(bytes, path);
}
match self.get_path(path)? {
Value::Number(n) => match n {
Number::F64(f) if f.is_finite() => Some(*f),
Number::I64(i) => Some(*i as f64),
Number::U64(u) => Some(*u as f64),
Number::F64(_) => None, },
_ => None,
}
}
#[must_use]
pub fn path_float_or(&self, path: &[&str], default: f64) -> f64 {
self.path_float(path).unwrap_or(default)
}
#[must_use]
pub fn path_bool(&self, path: &[&str]) -> Option<bool> {
if let Some(bytes) = self.bytes() {
return lazy::path_bool(bytes, path);
}
match self.get_path(path)? {
Value::Bool(b) => Some(*b),
_ => None,
}
}
#[must_use]
pub fn path_bool_or(&self, path: &[&str], default: bool) -> bool {
self.path_bool(path).unwrap_or(default)
}
#[must_use]
pub fn path_is_null(&self, path: &[&str]) -> bool {
if let Some(bytes) = self.bytes() {
return lazy::path_is_null(bytes, path);
}
matches!(self.get_path(path), Some(Value::Null))
}
#[must_use]
pub fn path_exists(&self, path: &[&str]) -> bool {
if let Some(bytes) = self.bytes() {
return lazy::path_exists(bytes, path);
}
self.get_path(path).is_some()
}
fn get_parsed_mut(&mut self) -> &mut Rc<Value> {
if let JsonInner::Lazy { bytes } = &self.inner {
let value = Self::parse_bytes(bytes).unwrap_or(Value::Null);
self.inner = JsonInner::Parsed(Rc::new(value));
}
match &mut self.inner {
JsonInner::Parsed(rc) => rc,
JsonInner::Lazy { .. } => unreachable!(),
}
}
#[must_use]
#[allow(clippy::needless_pass_by_value)] pub fn set(mut self, key: &str, value: Self) -> Self {
let inner_val = value.value().clone();
let rc = self.get_parsed_mut();
let val_mut = Rc::make_mut(rc);
if let Value::Object(obj) = val_mut {
obj.insert(key.to_string(), inner_val);
} else {
let mut obj = Object::new();
obj.insert(key.to_string(), inner_val);
*val_mut = Value::Object(obj);
}
self
}
#[must_use]
#[allow(clippy::needless_pass_by_value)] pub fn push(mut self, value: Self) -> Self {
let inner_val = value.value().clone();
let rc = self.get_parsed_mut();
let val_mut = Rc::make_mut(rc);
if let Value::Array(arr) = val_mut {
arr.push(inner_val);
} else {
let mut arr = Array::new();
arr.push(inner_val);
*val_mut = Value::Array(arr);
}
self
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
self.to_string().into_bytes()
}
}
impl std::fmt::Display for JsonValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.inner {
JsonInner::Lazy { bytes } => {
let s = std::str::from_utf8(bytes).unwrap_or("null");
f.write_str(s)
},
JsonInner::Parsed(v) => {
write!(f, "{}", miniserde::json::to_string(&**v))
},
}
}
}
impl std::fmt::Debug for JsonValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}