conf_json 0.1.4

A human editable configure file in JSON parser
Documentation
//! The Value enum that holds any valid JSON value.
//! 
//! This crate focus on configure file parsing, so all the methods are immutable
//!

use std::io;
use std::fmt::Debug;
use std::ops::Index;
use std::collections::HashMap;
use std::str::FromStr;

use crate::parser::{Parser, ParseError};

pub type ArrayType = Vec<Value>;
pub type ArrayIter<'a> = std::slice::Iter<'a, Value>;
pub type ObjectType = HashMap<String, Value>;
pub type ObjectIter<'a> = std::collections::hash_map::Iter<'a, String, Value>;

#[derive(Debug)]
pub enum Value {
	Null,
	Bool(bool),
	Number(f64),
	String(String),
	Array(ArrayType),
	Object(ObjectType),
}

impl Value {
	pub fn is_null(&self) -> bool {
		match self {
			Value::Null => true,
			_ => false,
		}
	}
	
	pub fn is_bool(&self) -> bool {
		match self {
			Value::Bool(_) => true,
			_ => false,
		}
	}

	/// If the `Value` type is Null, return false.
	/// If the `Value` type is Number, return true if is not zero.
	/// For `String` we would like to treat "yes" and "true" literal as true otherwise false.
	/// This is a pretty common scenario for configure file.
	/// And for `Array` and `Object` `as_bool()` means it's empty or not
	/// (maybe we should add `is_empty()` method).
	pub fn as_bool(&self) -> bool {
		match self {
			Value::Null => false,
			Value::Bool(v) => *v,
			Value::Number(v) => *v as i64 != 0,
			Value::String(v) => {
				let s = v.to_ascii_lowercase();
				s == "true" || s == "yes"
			}
			Value::Array(v) => !v.is_empty(),
			Value::Object(v) => !v.is_empty(),
		}
	}

	pub fn is_number(&self) -> bool {
		match self {
			Value::Number(_) => true,
			_ => false,
		}
	}

	/// If the `Value` type is Null, return 0.
	/// For Array and Object, return 0 for no meaning.
	/// Otherwise try to convert to i64.
	///
	/// Why not return Option::None in this situation?
	/// This crate is mainly for configure fetching, usually we know the type of a key,
	/// hence, this behavior may bring some convenient.
	/// Just for now, may change to `Option` in future version.
	pub fn as_i64(&self) -> i64 {
		match self {
			Value::Null => 0,
			Value::Bool(v) => if *v { 1 } else { 0 },
			Value::Number(v) => *v as i64,
			Value::String(v) => {
				// try to prase string as i64, otherwise return 0
				if let Ok(i) = i64::from_str(v) {
					i
				} else {
					0
				}
			}
			Value::Array(_) => 0,
			Value::Object(_) => 0,
		}
	}

	/// If the `Value` type is Null, return 0.0.
	/// For Array and Object, return 0.0 for no meaning.
	/// Otherwise try to convert to f64.
	pub fn as_f64(&self) -> f64 {
		match self {
			Value::Null => 0.0,
			Value::Bool(v) => if *v { 1.0 } else { 0.0 },
			Value::Number(v) => *v,
			Value::String(v) => {
				if let Ok(f) = f64::from_str(v) {
					f
				} else {
					0.0
				}
			}
			Value::Array(_) => 0.0,
			Value::Object(_) => 0.0,
		}
	}

	pub fn is_string(&self) -> bool {
		match self {
			Value::String(_) => true,
			_ => false,
		}
	}

	/// For `Null` and `Bool`, return the literal.
	/// For `Array` and `Object`, return empty string for no meaning.
	pub fn as_str(&self) -> &str {
		match self {
			Value::Null => "null",
			Value::Bool(v) => if *v { "true" } else { "false" },
			Value::Number(_) => "",
			Value::String(v) => v,
			Value::Array(_) => "",
			Value::Object(_) => "",
		}
	}

	pub fn len(&self) -> usize {
		match self {
			Value::Null => 0,
			Value::Bool(_) => 1,
			Value::Number(_) => 1,
			Value::String(v) => v.len(),
			Value::Array(v) => v.len(),
			Value::Object(v) => v.len(),
		}
	}

	pub fn is_array(&self) -> bool {
		match self {
			Value::Array(_) => true,
			_ => false,
		}
	}

	pub fn is_object(&self) -> bool {
		match self {
			Value::Object(_) => true,
			_ => false,
		}
	}

	/// Check specified key exists in an Object or not.
	/// For other types, always return false.
	pub fn has(&self, key: &str) -> bool {
		match self {
			Value::Object(v) => v.get(key).is_some(),
			_ => false,
		}
	}

	/// Get a iterator visiting over JSON Array.
	/// panic if Value is not an Array type.
	pub fn iter_array(&self) -> ArrayIter {
		match self {
			Value::Array(v) => v.iter(),
			_ => panic!("call iter_array on non-array type"),
		}
	}

	/// Get a iterator visiting over JSON Object.
	/// panic if Value is not an Object type.
	pub fn iter_object(&self) -> ObjectIter {
		match self {
			Value::Object(v) => v.iter(),
			_ => panic!("call iter_object() on non-object type"),
		}
	}
}

/// Index a JSON Array by `usize`, otherwise panic
impl Index<usize> for Value {
	type Output = Value;

	fn index(&self, idx: usize) -> &Self::Output {
		match self {
			Value::Array(v) => {
				&v[idx]
			}
			_ => panic!("can not index[usize] non-array json value"),
		}
	}
}

/// Index a JSON Object by `&str`, otherwise panic
impl Index<&str> for Value {
	type Output = Value;

	fn index(&self, idx: &str) -> &Self::Output {
		match self {
			Value::Object(v) => {
				&v[idx]
			}
			_ => panic!("can not index[str] non-object json value"),
		}
	}
}

impl FromStr for Value {
	type Err = ParseError;

	fn from_str(s: &str) -> Result<Self, Self::Err> {
		Parser::new(s.as_bytes()).parse()
	}
}

/// Convert from std::io::Read, panic when goes wrong
impl<T: io::Read + Debug> From<T> for Value {
	fn from(src: T) -> Self {
		Parser::new(src).parse().unwrap()
	}
}