///|
/// Deterministic JSON serialization: object keys are sorted, array order is preserved.
pub fn canonical(value : Json) -> String raise CaseError {
  validate_value(value, 0)
  canonical_unchecked(value)
}

///|
fn canonical_unchecked(value : Json) -> String {
  match value {
    Object(fields) => {
      let keys = fields.keys().to_array()
      keys.sort_by((a, b) => a.lexical_compare(b))
      let parts = keys.map(key => {
        key.to_json().stringify() + ":" + canonical_unchecked(fields[key])
      })
      "{" + parts.join(",") + "}"
    }
    Array(xs) => "[" + xs.map(canonical_unchecked).join(",") + "]"
    _ => value.stringify()
  }
}

///|
fn copy_value(value : Json) -> Json {
  match value {
    Object(fields) => {
      let copied : Map[String, Json] = Map([])
      for key, item in fields {
        copied[key] = copy_value(item)
      }
      copied.to_json()
    }
    Array(xs) => xs.map(copy_value).to_json()
    _ => value
  }
}