///|
/// Error returned when deserialization of a `Value` into a typed target fails.
pub(all) struct DeserializeError {
message : String
} derive(Debug, Eq)
///|
/// Create a `DeserializeError` with a free-form message.
pub fn DeserializeError::new(message : String) -> DeserializeError {
{ message, }
}
///|
/// Create an error for a type mismatch (e.g. expected a list but got a string).
pub fn DeserializeError::type_mismatch(
expected : String,
actual : String,
) -> DeserializeError {
{ message: "expected \{expected}, got \{actual}" }
}
///|
/// Create an error for a failed string-to-primitive conversion.
pub fn DeserializeError::parse_error(
value : String,
target_type : String,
) -> DeserializeError {
{ message: "cannot parse '\{value}' as \{target_type}" }
}
///|
/// Create an error for a missing dictionary field.
pub fn DeserializeError::missing_field(field_name : String) -> DeserializeError {
{ message: "missing field: \{field_name}" }
}
///|
/// A typed view over a parsed `Value` that extracts MoonBit values.
pub(all) struct Deserializer {
value : Value
} derive(Debug, Eq)
///|
/// Wrap a `Value` for typed extraction.
pub fn Deserializer::new(value : Value) -> Deserializer {
{ value, }
}
///|
/// Return the raw `Value` this deserializer wraps.
pub fn Deserializer::as_value(self : Deserializer) -> Value {
self.value
}
///|
fn value_type_name(value : Value) -> String {
match value {
Value::String(_) => "string"
Value::List(_) => "list"
Value::Dict(_) => "dictionary"
}
}
///|
/// Extract the value as a `String`. Errors if the underlying value is not a string.
pub fn Deserializer::expect_string(
self : Deserializer,
) -> Result[String, DeserializeError] {
match self.value {
Value::String(s) => Ok(s)
v => Err(DeserializeError::type_mismatch("string", value_type_name(v)))
}
}
///|
/// Extract the value as an `Int` by parsing the string representation.
///
/// # Errors
/// Returns `DeserializeError::type_mismatch` when the value is not a string,
/// or `DeserializeError::parse_error` when the string cannot be parsed as
/// an `Int`.
pub fn Deserializer::expect_int(
self : Deserializer,
) -> Result[Int, DeserializeError] {
match self.expect_string() {
Ok(s) =>
try @string.from_str(s.trim()) |> Ok catch {
_ => Err(DeserializeError::parse_error(s, "Int"))
}
Err(e) => Err(e)
}
}
///|
/// Extract the value as an `Int64` by parsing the string representation.
///
/// # Errors
/// Returns `DeserializeError::type_mismatch` when the value is not a string,
/// or `DeserializeError::parse_error` when the string cannot be parsed as
/// an `Int64`.
pub fn Deserializer::expect_int64(
self : Deserializer,
) -> Result[Int64, DeserializeError] {
match self.expect_string() {
Ok(s) =>
try @string.from_str(s.trim()) |> Ok catch {
_ => Err(DeserializeError::parse_error(s, "Int64"))
}
Err(e) => Err(e)
}
}
///|
/// Extract the value as a `Double` by parsing the string representation.
///
/// # Errors
/// Returns `DeserializeError::type_mismatch` when the value is not a string,
/// or `DeserializeError::parse_error` when the string cannot be parsed as
/// a `Double`.
pub fn Deserializer::expect_double(
self : Deserializer,
) -> Result[Double, DeserializeError] {
match self.expect_string() {
Ok(s) =>
try @string.from_str(s.trim()) |> Ok catch {
_ => Err(DeserializeError::parse_error(s, "Double"))
}
Err(e) => Err(e)
}
}
///|
/// Extract the value as a `Bool`.
///
/// Accepts `true`/`True`/`TRUE`/`yes`/`Yes`/`YES` for `true`, and
/// `false`/`False`/`FALSE`/`no`/`No`/`NO` for `false`.
/// Errors if the value is not a string or the text is not a recognised
/// boolean representation.
pub fn Deserializer::expect_bool(
self : Deserializer,
) -> Result[Bool, DeserializeError] {
match self.expect_string() {
Ok(s) =>
match s.trim() {
"true" | "True" | "TRUE" | "yes" | "Yes" | "YES" => Ok(true)
"false" | "False" | "FALSE" | "no" | "No" | "NO" => Ok(false)
_ => Err(DeserializeError::parse_error(s, "Bool"))
}
Err(e) => Err(e)
}
}
///|
/// Extract the underlying list items.
/// Errors if the value is not a `List`.
pub fn Deserializer::expect_list(
self : Deserializer,
) -> Result[Array[Value], DeserializeError] {
match self.value {
Value::List(items) => Ok(items)
v => Err(DeserializeError::type_mismatch("list", value_type_name(v)))
}
}
///|
/// Extract the underlying dictionary pairs.
/// Errors if the value is not a `Dict`.
pub fn Deserializer::expect_dict(
self : Deserializer,
) -> Result[Array[(String, Value)], DeserializeError] {
match self.value {
Value::Dict(pairs) => Ok(pairs)
v => Err(DeserializeError::type_mismatch("dictionary", value_type_name(v)))
}
}
///|
/// Retrieve a field from the dictionary by key name.
/// Errors if the value is not a `Dict` or the key is missing.
pub fn Deserializer::get_field(
self : Deserializer,
key : String,
) -> Result[Deserializer, DeserializeError] {
match self.expect_dict() {
Ok(pairs) => {
for pair in pairs {
let (k, v) = pair
if k == key {
return Ok(Deserializer::new(v))
}
}
Err(DeserializeError::missing_field(key))
}
Err(e) => Err(e)
}
}
///|
/// Check whether a key exists in the dictionary.
/// Returns `false` if the underlying value is not a dictionary.
pub fn Deserializer::has_field(self : Deserializer, key : String) -> Bool {
match self.value {
Value::Dict(pairs) => {
for pair in pairs {
if pair.0 == key {
return true
}
}
false
}
_ => false
}
}
///|
/// Return the list of field names if the value is a dictionary.
/// Returns an empty array otherwise.
pub fn Deserializer::field_names(self : Deserializer) -> Array[String] {
match self.value {
Value::Dict(pairs) => pairs.map(fn(p) { p.0 })
_ => []
}
}
///|
/// Deserialize a list where each element is decoded with `f`.
///
/// # Parameters
/// * `d` — A deserializer that should wrap a `List` value.
/// * `f` — A function that receives a fresh `Deserializer` for each element.
///
/// # Returns
/// The array of decoded elements on success.
/// Returns `DeserializeError::type_mismatch` when the underlying value is not
/// a list.
pub fn[T] deserialize_list(
d : Deserializer,
f : (Deserializer) -> Result[T, DeserializeError],
) -> Result[Array[T], DeserializeError] {
match d.expect_list() {
Ok(items) => {
let results : Array[T] = []
for item in items {
let elem_d = Deserializer::new(item)
match f(elem_d) {
Ok(val) => results.push(val)
Err(e) => return Err(e)
}
}
Ok(results)
}
Err(e) => Err(e)
}
}
///|
/// Deserialize an optional field.
///
/// An empty string (`""`) is mapped to `None`; any other value is passed
/// through `f` and wrapped in `Some`.
pub fn[T] Deserializer::expect_optional(
self : Deserializer,
f : (Deserializer) -> Result[T, DeserializeError],
) -> Result[T?, DeserializeError] {
match self.value {
Value::String("") => Ok(None)
_ =>
match f(self) {
Ok(val) => Ok(Some(val))
Err(e) => Err(e)
}
}
}
///|
/// Convert a `Value` into a typed value using `f`.
///
/// # Parameters
/// * `value` — The parsed value to deserialize.
/// * `f` — A function that extracts the typed result from a `Deserializer`.
pub fn[T] deserialize_value(
value : Value,
f : (Deserializer) -> Result[T, DeserializeError],
) -> Result[T, DeserializeError] {
f(Deserializer::new(value))
}
///|
/// Parse a NestedText string and deserialize the result into a typed value
/// using `f`.
///
/// Parse errors (format / syntax) are returned as `NestedTextError`;
/// deserialization errors are wrapped in a `NestedTextError` with
/// `ErrorKind::DeserializationError`.
pub fn[T] deserialize_str(
input : String,
top : Top,
f : (Deserializer) -> Result[T, DeserializeError],
) -> Result[T, NestedTextError] {
match loads(input, top) {
Ok(Some(v)) =>
match f(Deserializer::new(v)) {
Ok(t) => Ok(t)
Err(e) => Err(NestedTextError::new(DeserializationError, e.message))
}
Ok(None) =>
match f(Deserializer::new(Value::String(""))) {
Ok(t) => Ok(t)
Err(e) => Err(NestedTextError::new(DeserializationError, e.message))
}
Err(e) => Err(e)
}
}