///|
/// Additional audit metrics beyond the structural counters in `JsonStats`.
pub(all) struct JsonProfile {
stats : JsonStats
key_count : Int
unique_key_count : Int
max_key_length : Int
max_string_length : Int
negative_zero_count : Int
canonical_issue_count : Int
}
///|
priv struct ProfileState {
keys : Array[String]
mut max_key_length : Int
mut max_string_length : Int
mut negative_zero_count : Int
}
///|
pub fn profile_value(value : JsonValue) -> JsonProfile {
let stats = JsonStats::empty()
collect_stats(value, 0, stats)
let state = {
keys: [],
max_key_length: 0,
max_string_length: 0,
negative_zero_count: 0,
}
collect_profile_values(value, state)
{
stats,
key_count: state.keys.length(),
unique_key_count: count_unique_keys(state.keys),
max_key_length: state.max_key_length,
max_string_length: state.max_string_length,
negative_zero_count: state.negative_zero_count,
canonical_issue_count: canonical_issues(value).length(),
}
}
///|
pub fn profile_document(input : String) -> Result[JsonProfile, String] {
let value = match parse(input) {
Ok(value) => value
Err(error) => return Err(error.message())
}
Ok(profile_value(value))
}
///|
fn collect_profile_values(value : JsonValue, state : ProfileState) -> Unit {
match value {
String(string) =>
if string.length() > state.max_string_length {
state.max_string_length = string.length()
}
Number(number) =>
if number == "-0" ||
number == "-0.0" ||
number == "-0e0" ||
number == "-0E0" {
state.negative_zero_count = state.negative_zero_count + 1
}
Array(values) =>
for child in values {
collect_profile_values(child, state)
}
Object(entries) =>
for entry in entries {
state.keys.push(entry.0)
if entry.0.length() > state.max_key_length {
state.max_key_length = entry.0.length()
}
collect_profile_values(entry.1, state)
}
_ => ()
}
}
///|
fn count_unique_keys(keys : Array[String]) -> Int {
let unique : Array[String] = []
for key in keys {
if !contains_string(unique, key) {
unique.push(key)
}
}
unique.length()
}
///|
fn contains_string(values : Array[String], target : String) -> Bool {
for value in values {
if value == target {
return true
}
}
false
}
///|
pub fn JsonProfile::to_json(self : JsonProfile) -> String {
"{\"stats\":" +
self.stats.to_json() +
",\"key_count\":" +
self.key_count.to_string() +
",\"unique_key_count\":" +
self.unique_key_count.to_string() +
",\"max_key_length\":" +
self.max_key_length.to_string() +
",\"max_string_length\":" +
self.max_string_length.to_string() +
",\"negative_zero_count\":" +
self.negative_zero_count.to_string() +
",\"canonical_issue_count\":" +
self.canonical_issue_count.to_string() +
"}"
}