///|
fn object_keys(
value : Json,
allowed : Array[String],
) -> Map[String, Json] raise KineticsError {
let object = match value {
Object(o) => o
_ => raise Invalid("JSON object expected")
}
if object.length() != allowed.length() {
raise Invalid("missing or extra JSON keys")
}
for key in object.keys() {
if !allowed.contains(key) {
raise Invalid("unknown JSON key")
}
}
object
}
///|
fn required(
object : Map[String, Json],
key : String,
) -> Json raise KineticsError {
match object.get(key) {
Some(v) => v
None => raise Invalid("required JSON key missing")
}
}
///|
fn number(value : Json) -> Double raise KineticsError {
match value {
Number(x, ..) => {
finite(x, "JSON number")
x
}
_ => raise Invalid("JSON number expected")
}
}
///|
fn array(value : Json) -> Array[Json] raise KineticsError {
match value {
Array(a) => a
_ => raise Invalid("JSON array expected")
}
}
///|
fn coefficients(value : Json) -> Array[Int32] raise KineticsError {
let a = array(value)
if a.is_empty() || a.length() > 64 {
raise Invalid("coefficient vector size")
}
let result : Array[Int32] = []
for entry in a {
let x = number(entry)
if x < 0.0 || x > 1000000.0 || x != x.to_int().to_double() {
raise Invalid("coefficient must be a supported integer")
}
result.push(x.to_int())
}
result
}
///|
/// Versioned model schema: exactly schema/species/volume/reactions; each
/// channel exactly input/output/rate. Unknown keys and duplicate keys fail.
pub fn network_from_json(text : String) -> Network raise KineticsError {
let o = object_keys(guarded_json(text), [
"schema", "species", "volume", "reactions",
])
if number(required(o, "schema")) != 1.0 {
raise Invalid("unsupported model schema version")
}
let names : Array[String] = []
for name in array(required(o, "species")) {
match name {
String(s) => names.push(s)
_ => raise Invalid("species name must be a string")
}
if names.length() > 64 {
raise Budget("JSON species budget")
}
}
let reactions : Array[Reaction] = []
for value in array(required(o, "reactions")) {
let r = object_keys(value, ["input", "output", "rate"])
reactions.push(
Reaction::new(
coefficients(required(r, "input")),
coefficients(required(r, "output")),
number(required(r, "rate")),
),
)
if reactions.length() > 1024 {
raise Budget("JSON reaction budget")
}
}
Network::new(names, reactions, volume=number(required(o, "volume")))
}
///|
pub fn Network::to_json(self : Network) -> Json {
let channels = self.reactions.map(r => {
let j : Json = { "input": r.input, "output": r.output, "rate": r.rate }
j
})
{
"schema": 1,
"species": self.names,
"volume": self.volume,
"reactions": channels,
}
}