///|
fn json_identifier(value : String, component : Bool) -> Unit raise SdmxError {
if value.is_empty() || value.length() > 128 {
raise Unsupported(feature="JSON2 identifier length")
}
for i, ch in value.to_array() {
let letter = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
let ordinary = letter || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-'
if (component && i == 0 && !letter) ||
(!ordinary && (component || (ch != '@' && ch != '$'))) {
raise Unsupported(feature="identifier outside JSON2 grammar: \{value}")
}
}
}
///|
fn utc_timestamp(text : String) -> Unit raise SdmxError {
if text.length() != 20 ||
text[10] != 'T' ||
text[13] != ':' ||
text[16] != ':' ||
text[19] != 'Z' {
raise Invalid(path="prepared", reason="expected YYYY-MM-DDTHH:MM:SSZ")
}
ignore(PeriodValue::parse(text[:10].to_owned()))
if period_digits(text, 11, 13) > 23 ||
period_digits(text, 14, 16) > 59 ||
period_digits(text, 17, 19) > 59 {
raise Invalid(path="prepared", reason="clock field out of range")
}
}
///|
fn wire_format(component : Component) -> Json {
let data_type = match component.representation {
Text | Coded(_) => "String"
Decimal => "Decimal"
Integer => "Integer"
Period => "ObservationalTimePeriod"
}
{ "dataType": data_type }
}
///|
fn wire_relationship(component : Component, structure : Structure) -> Json {
match component.role {
Attribute(keys) => {
if keys.is_empty() {
return { "dataflow": {} }
}
if keys == structure.dimensions.map(c => c.id) {
return { "observation": {} }
}
{ "dimensions": keys.map(Json::string) }
}
_ => abort("attribute required")
}
}
///|
fn wire_component_json(
component : Component,
structure : Structure,
) -> Map[String, Json] {
let object : Map[String, Json] = Map([])
object["id"] = Json::string(component.id)
if !component.name.is_empty() {
object["name"] = Json::string(component.name)
}
match component.representation {
Coded(codes) => {
let values : Array[Json] = codes
.entries()
.map(pair => {
"id": pair.0,
"name": if pair.1.is_empty() {
pair.0
} else {
pair.1
},
})
object["values"] = Json::array(values)
}
_ => object["format"] = wire_format(component)
}
match component.role {
Attribute(_) =>
object["relationship"] = wire_relationship(component, structure)
Measure => ()
_ => return object
}
object["isMandatory"] = Json::boolean(component.required)
object
}
///|
fn wire_indexed_value(
component : Component,
value : String?,
indexes : Map[String, Int32],
) -> Json {
match value {
None => Json::null()
Some(text) =>
match component.representation {
Coded(_) => Json::number(indexes[text].to_double())
_ => Json::string(text)
}
}
}
///|
fn code_indexes(component : Component) -> Map[String, Int32] {
let indexes : Map[String, Int32] = Map([])
if component.representation is Coded(codes) {
for i, pair in codes.entries() {
indexes[pair.0] = i
}
}
indexes
}
///|
pub fn DataMessage::encode_json2(
self : DataMessage,
id~ : String,
sender~ : String,
prepared~ : String,
) -> String raise SdmxError {
json_identifier(id, false)
json_identifier(sender, false)
utc_timestamp(prepared)
let structure = self.dataset.structure
for component in structure.components() {
json_identifier(component.id, true)
if component.representation is Coded(codes) {
for pair in codes.entries() {
json_identifier(pair.0, false)
}
}
}
let dimension_indexes : Array[Map[String, Int32]] = []
let dimensions : Array[Json] = []
for position, component in structure.dimensions {
let object = wire_component_json(component, structure)
let entries = match component.representation {
Coded(codes) => codes.entries()
_ => self.dataset.dimension_values(component.id).map(code => (code, code))
}
let values : Array[Json] = entries.map(pair => {
match component.representation {
Coded(_) =>
{
"id": pair.0,
"name": if pair.1.is_empty() {
pair.0
} else {
pair.1
},
}
_ => { "value": pair.0 }
}
})
object["values"] = Json::array(values)
object["keyPosition"] = Json::number(position.to_double())
dimensions.push(Json::object(object))
let indexes : Map[String, Int32] = Map([])
for i, pair in entries {
indexes[pair.0] = i
}
dimension_indexes.push(indexes)
}
let measures : Array[Json] = structure.measures.map(c => {
Json::object(wire_component_json(c, structure))
})
let attributes : Array[Json] = structure.attributes.map(c => {
Json::object(wire_component_json(c, structure))
})
let measure_indexes = structure.measures.map(code_indexes)
let attribute_indexes = structure.attributes.map(code_indexes)
let observations : Map[String, Json] = Map([])
for row in self.dataset.rows {
let key = row.key
.mapi((i, value) => dimension_indexes[i][value].to_string())
.join(":")
let tuple : Array[Json] = []
for i, component in structure.measures {
tuple.push(
wire_indexed_value(component, row.measures[i], measure_indexes[i]),
)
}
for i, component in structure.attributes {
tuple.push(
wire_indexed_value(component, row.attributes[i], attribute_indexes[i]),
)
}
for index in self.row_notes.get(row.key_token()).unwrap_or([]) {
tuple.push(Json::number(index.to_double()))
}
observations[key] = Json::array(tuple)
}
let (class_name, relation) = match self.kind {
Dataflow => ("datastructure.Dataflow", "dataflow")
Datastructure => ("datastructure.DataStructure", "datastructure")
Dataprovision =>
("provisionagreement.ProvisionAgreement", "provisionagreement")
}
let urn = "urn:sdmx:org.sdmx.infomodel.\{class_name}=\{self.reference.format()}"
let annotation_values : Array[Json] = self.notes.map(note => {
parse_bounded_json(note.content, structure.limits)
})
let embedded : Map[String, Json] = Map([
("links", Json::array([{ "urn": urn, "rel": relation }])),
("dimensions", { "observation": dimensions }),
("measures", { "observation": measures }),
("attributes", { "observation": attributes }),
])
if !annotation_values.is_empty() {
embedded["annotations"] = Json::array(annotation_values)
}
let document : Json = {
"meta": {
"schema": "https://json.sdmx.org/2.0.0/sdmx-json-data-schema.json",
"id": id,
"sender": { "id": sender },
"prepared": prepared,
},
"data": {
"structures": [Json::object(embedded)],
"dataSets": [
{
"structure": 0,
"links": [{ "urn": urn, "rel": relation }],
"action": "Information",
"observations": Json::object(observations),
},
],
},
}
canonical_json(document, structure.limits)
}