JSON Labs
Syside supports bi-directional JSON serialization.
Export
Serialization produces a mostly specification compliant JSON with a few minor differences:
not all implicit elements are constructed which will fail for attributes that are defined as non-null in the standard JSON schema, e.g.
Function::result;references can be serialized with relative
@urifield for references to elements from other documents by passinginclude_cross_ref_uris=True(default) tojson.dumps. This brings JSON exports in-line with corresponding XMI exports used by the Pilot implementation where references are always exported as<relative URI>#<element id>. Additionally, such exports enable much faster deserialization because cross references are transparent and do not require searching the world for their resolution.Note
A custom URI scheme to reference elements relative to their owning SysML packages (not the elements) may be added in the future as package manager support is implemented. This would remove the dependence on filesystem layout for deserialization.
json: str = syside.json.dumps(
element, options=syside.SerializationOptions.minimal()
)
SerializationOptions controls what gets
serialized. Using minimal is
recommended for most use cases.
Warning
JSONs typically take 100-1000 times more space than the original textual notation even with minimal options and are opaque to human readers. Textual notation is recommended instead of JSON whenever possible.
Import
JSON deserialization (import) works on the same JSON files as were exported. However, in the interest of keeping the initial implementation simple there are a few limitations:
root node is inferred as:
the first
Namespacewithout an owning relationship,the last ancestor of the first element in the array following either owning namespaces, owning related elements, or owning relationships,
the first element in the array otherwise;
references to elements from other
Documentsmay contain a@urifield. The URI of theDocumentmodel is deserialized into will be used to resolve relative URI references, otherwise an empty URI will be passed to the resolver callback;deserialization may be lossy because the specification dumps all owned elements into
owned_relationshipsandowned_related_elementsattributes which lose the more fine grained information stored in the model by Syside. For example,SendActionUsagereceiver,payload, andsenderare all parameters toReferenceUsage, only disambiguated by their relative position, so if one is missing the others may be deserialized into different members.
Note that deserialization ignores majority of fields present in the JSON schema,
including all derived fields with the exception of name and shortName. Therefore
users may wish to export JSONs with minimal export options to reduce memory usage and
improve performance.
Currently, to support cyclical JSON imports, foreign references are not resolved eagerly and instead must be resolved after deserialization:
result, document = syside.json.loads(
model_json, "file:///home/user/test.sysml"
)
# ... collect all valid element ids for linking, e.g.
map = syside.IdMap()
map.insert_or_assign(result.document) # and other documents
# resolve pending foreign references
report, success = result.link(map)
DeserializedModel.link accepts any callable
(uri: str, element_id: UUID) -> Element | None, not just IdMap. Note that for unowned references without @uri field, uri will
be an empty string.
Low-level
High-level json.dumps and json.loads are built on top of low-level API for convenience. Performance of
repeated JSON serializations can be improved using low-level API:
Exporting model rooted at
element: Element:writer = syside.JsonStringWriter() serializer = syside.Serializer() report: syside.SerdeReport[syside.Element] = serializer.accept( element, writer, options=syside.SerializationOptions.minimal() ) output: str = writer.result
Writer can be reused by calling
.clear().serializecan be used in place ofSerializer.acceptif performance is not a concern.Importing
json_str: strintotarget_document: Document:reader = syside.JsonReader() deserializer = syside.Deserializer(target_document) with reader.bind(json_str) as contents: model: syside.DeserializedModel report: syside.SerdeReport[ syside.DocumentSegment | str | syside.Element ] model, report = deserializer.accept( contents, syside.DESERIALIZE_STANDARD )
readercan be reused by binding multiple times, but not recursively, foreign references need to be linked as in the previous section snippet.deserializecan be used in place ofDeserializer.acceptif performance is not a concern. The last parameterDESERIALIZE_STANDARDcontrols the attribute names that are used in deserialization. The former looks for standard definedcamelCasedattributes names, e.g.shortName, whileDESERIALIZE_INTERNAL–snake_casednames that also match Python API naming convention, e.g.short_name.