///|
/// Return `json` without the object members whose value is `null`.
///
/// A member holding `null` says no more than the member being absent, so a
/// document that carries them is one where `null` stood in for "no value": worth
/// writing once and not worth reading every time. The members that stay keep the
/// order they were written in, and every other value is left exactly as it was —
/// including an object that is left empty once its null members are gone, since
/// emptying out is a separate thing to ask for.
///
/// An array keeps every item it had, null ones included. An array is a sequence
/// and its positions are part of what it says, so dropping an item out of the
/// middle would renumber everything after it: a null inside an object is a name
/// with no value and removing it removes a name, while a null inside an array is
/// a value in a place and removing it changes what the items around it are.
///
/// As with sorting and trimming, the tree handed in is left as it was found:
/// every container is rebuilt rather than edited.
pub fn prune_null(json : @pjson.Json) -> @pjson.Json {
match json {
Object(members~) => {
let out : Array[(String, @pjson.Json)] = []
for entry in members {
let (key, value) = entry
match value {
Null => ()
_ => out.push((key, prune_null(value)))
}
}
Object(members=out)
}
Array(items~) => Array(items=items.map(fn(item) { prune_null(item) }))
// A bare `null` is the document, not a member of one, so there is nothing
// holding it that could be dropped.
leaf => leaf
}
}
///|
/// Return `json` with every empty object and every empty array removed.
///
/// A container with nothing in it is a container that says nothing, so they go
/// wherever they are — as a member of an object or as an item of an array — and
/// the containers left empty by that go too, however far up the document that
/// reaches: `{"a":{"b":{}}}` comes back as `{}` in one pass.
///
/// The document itself is kept even when it ends up empty. It is not a member of
/// anything, so there is no container to be told that it has nothing in it, and
/// removing it would leave nothing at all where a document was asked for: `{}`
/// and `[]` come back as they were.
pub fn prune_empty(json : @pjson.Json) -> @pjson.Json {
match json {
Object(members~) => {
let out : Array[(String, @pjson.Json)] = []
for entry in members {
let (key, value) = entry
match prune_empty_value(value) {
Some(value) => out.push((key, value))
None => ()
}
}
Object(members=out)
}
Array(items~) => {
let out : Array[@pjson.Json] = []
for item in items {
match prune_empty_value(item) {
Some(item) => out.push(item)
None => ()
}
}
Array(items=out)
}
leaf => leaf
}
}
///|
/// Prune one value, answering `None` when it is an empty container.
///
/// This is where a container is actually dropped, and it is done on the way out
/// of the recursion: by the time a container is looked at here, everything
/// inside it has already been pruned, so one that is empty now is one that was
/// empty or emptied. That is what makes the cascade work — the caller drops this
/// one and then finds itself empty in turn.
///
/// The distinction between `None` and an empty container is the distinction
/// between a value inside a document and the document itself, which is why the
/// two `prune_empty` arms above build their container unconditionally.
fn prune_empty_value(value : @pjson.Json) -> @pjson.Json? {
match prune_empty(value) {
Object(members=inner) =>
if inner.is_empty() {
None
} else {
Some(Object(members=inner))
}
Array(items~) => if items.is_empty() { None } else { Some(Array(items~)) }
other => Some(other)
}
}