# Minimal JSON round-trip with external/library references produces unresolved-reference warnings and non-identical derived relationships after reconstruction

**URL:** <https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505>\
**Category:** Bug Reports\
**Tags:** automator\
**Created:** [June 13, 2026, 6:00am UTC](https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505 "2026-06-13T06:00:51Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Robert\_Karban](https://yyz2.discourse-cdn.com/flex008/user_avatar/forum.sensmetry.com/robert_karban/32/119_2.png) [@Robert\_Karban](https://forum.sensmetry.com/u/Robert_Karban)\
**Post date:** [June 13, 2026, 6:00am UTC](https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505/1 "2026-06-13T06:00:51Z")

</div>

Summary:

When a model is exported to minimal JSON with include\_cross\_ref\_uris=False, then re-imported with syside.json.loads(…), models that reference stdlib/library  
elements emit unresolved-reference warnings during deserialization.

A stronger recovery path:

1. syside.json.loads(…)
2. DeserializedModel.link(IdMap of default environment docs)
3. syside.Sema().resolve(…)
4. syside.serialize(…, full options)

recovers the model much more accurately, including implied relationships and total element counts. However, the reconstructed full JSON is still not always  
identical to the original full export from the text-loaded model. The remaining differences are localized in derived relationship fields such as feature,  
featureMembership, inheritedFeature, usage, and some Subsetting targets.

Minimal reproducer for unresolved-reference warnings:

import warnings  
import syside

src = “package P { private import ScalarValues::\*; }”

model, diagnostics = syside.load\_model(sysml\_source=src)  
assert not diagnostics.contains\_errors(warnings\_as\_errors=True)

json\_options = syside.JsonStringOptions()  
json\_options.include\_cross\_ref\_uris = False  
json\_options.indent = False  
writer = syside.JsonStringWriter(json\_options)

options = syside.SerializationOptions.minimal()

with model.user\_docs[0].lock() as locked:  
syside.serialize(locked.root\_node, writer, options)  
raw\_min = writer.result

with warnings.catch\_warnings(record=True) as wlist:  
warnings.simplefilter(“always”)  
deserialized\_model, \_ = syside.json.loads(raw\_min, “memory:///import.sysml”)

print([str(w.message) for w in wlist])

Minimal reproducer for external typed feature warning:

import warnings  
import syside

src = “package P { part x { attribute mass :\> ISQBase::mass; } }”

model, diagnostics = syside.load\_model(sysml\_source=src)  
assert not diagnostics.contains\_errors(warnings\_as\_errors=True)

json\_options = syside.JsonStringOptions()  
json\_options.include\_cross\_ref\_uris = False  
json\_options.indent = False  
writer = syside.JsonStringWriter(json\_options)

options = syside.SerializationOptions.minimal()

with model.user\_docs[0].lock() as locked:  
syside.serialize(locked.root\_node, writer, options)  
raw\_min = writer.result

with warnings.catch\_warnings(record=True) as wlist:  
warnings.simplefilter(“always”)  
deserialized\_model, \_ = syside.json.loads(raw\_min, “memory:///import.sysml”)

print([str(w.message) for w in wlist])

Observed facts:

- The unresolved IDs are external/library elements, not missing local model elements.
- These IDs do exist in Environment.get\_default().
- Examples observed:
  - 40bb440c-… → ScalarValues
  - 2a413ac9-… → SI
  - 95bd17de-… → USCustomaryUnits
  - 9af08108-… → ISQBase::mass

Recovery path that improves fidelity:

import syside

env = syside.Environment.get\_default()

id\_map = syside.IdMap()  
for mutex in env.documents:  
with mutex.lock() as dep:  
id\_map.insert\_or\_assign(dep)

deserialized\_model.link(id\_map)  
syside.Sema().resolve([deserialized\_model.document], env.index(), env.lib)

Actual behavior:

- syside.json.loads(…) emits unresolved-reference warnings when minimal JSON contains external/library references serialized without cross-ref URIs.
- The link(IdMap) + Sema.resolve(…) workaround reconstructs implied relationships and usually restores overall structure much better.
- But reconstructed full JSON can still differ from the original full export from the text-loaded model.

Real-model evidence:

Using Flashlight.sysml:

- Original full export and reconstructed export had:

- Remaining differences were limited to 13 type/field combinations

- The differences were localized to derived relationship fields, including:

Expected behavior:

One of these should be true:

1. JSON exported with include\_cross\_ref\_uris=False should be documented as not cleanly round-trippable for models with external/library references.
2. syside.json.loads(…) should avoid unresolved-reference warnings for references that are expected to be resolved later from the default environment.
3. The combination of JSON deserialization, environment linking, and semantic resolution should reconstruct the same derived relationship bindings as the  
original text-loaded model, if minimal JSON is intended to be a lossless model persistence format.

Likely root cause:

- With include\_cross\_ref\_uris=False, external references are serialized as bare @id references.
- During json.loads(…), these become pending references with empty URI.
- This is not enough information for deserialization-time resolution.
- Later environment-based linking and semantic resolution recover most of the structure, but some derived relationships appear to be rebound differently from  
the original text-loaded model.

Why this matters:

Minimal JSON appears to work as a compact persistence format, but for models with external/library references it is currently:

- noisy at deserialization time due to warnings
- not fully stable under round-trip reconstruction, even with environment linking and semantic resolution

[Flashlight.sysml](https://forum.sensmetry.com/uploads/short-url/17E29Rf9Iwe5YUNlaJJXSFpq6co.sysml) (19.6 KB)

---

<div class="post-metadata">

**Author:** ![Daumantas](https://yyz2.discourse-cdn.com/flex008/user_avatar/forum.sensmetry.com/daumantas/32/19_2.png) [@Daumantas](https://forum.sensmetry.com/u/Daumantas)\
**Post date:** [June 15, 2026, 6:30am UTC](https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505/2 "2026-06-15T06:30:21Z")

</div>

Hi,

This is the intended behaviour for **single** JSON deserialization as we cannot know whether the user intended to resolve references from multiple, potentially cyclically dependent, sources or not. Documentation of return value of [json.loads](https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505) clearly states that. That is why unresolved references are only warnings and not errors.

Note that there is also a project deserialization overload `json.loads` that accepts an iterable of `tuple[str | Url | Document, str]` which **will** resolve pending references **after** deserialization.

---

<div class="post-metadata">

**Author:** ![Robert\_Karban](https://yyz2.discourse-cdn.com/flex008/user_avatar/forum.sensmetry.com/robert_karban/32/119_2.png) [@Robert\_Karban](https://forum.sensmetry.com/u/Robert_Karban)\
**Post date:** [July 6, 2026, 1:06am UTC](https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505/3 "2026-07-06T01:06:16Z")

</div>

Hi @Daumantas,

I applied the recommended recovery path in our wrapper:

1. project-style `syside.json.loads([...])`
2. `DeserializedModel.link(...)` with default-environment docs in the `IdMap`
3. `syside.Sema().resolve(...)`
4. then either pretty-print to text or serialize to full JSON

Results:

- This fixes the text reconstruction issue from topic 512 in our round-trip path.
- It also removes the unresolved external/library reference warnings we were seeing when reconstructing text from minimal JSON.
- For many models, full JSON → text → full JSON → text is now stable after one printer pass.

However, we still see remaining gaps for model-based minimal → full JSON reconstruction:

- `Flashlight.sysml`
- multi-file Flashlight
- `Test5.sysml`
- `Test7.sysml`  
like:
- `feature`
- `featureMembership`
- `inheritedFeature`
- `usage`
- some `Subsetting` targets / related fields

For `Test5` / `Test7`, reconstructed text can also fail reparsing with metadata-related errors such as:

- `metadata-feature-annotated-element: Metadata feature cannot annotate Usage`
- incomplete semantic metadata / unresolved baseType diagnostics

So at this point the sema-recovery path clearly improves fidelity a lot, but it does not yet make minimal JSON reconstruction fully equivalent to direct full export for all models.

```auto
import json
import syside

def expand_minimal_json_to_full_json(document_sources):
"""
document_sources:
iterable of (url, json_string), one per root document
example: [("memory:///Flashlight.sysml", raw_min_json)]
"""
_project_model, deserialized_results = syside.json.loads(document_sources)

```

```auto
  env = syside.Environment.get_default()
  id_map = syside.IdMap()

  for mutex in env.documents:
      with mutex.lock() as dep:
          id_map.insert_or_assign(dep)

  docs_to_resolve = []
  for deserialized_model, _report in deserialized_results:
      docs_to_resolve.append(deserialized_model.document)
      with deserialized_model.document.mutex.lock() as locked_document:
          id_map.insert_or_assign(locked_document)

  for deserialized_model, _report in deserialized_results:
      deserialized_model.link(id_map)

  syside.Sema().resolve(
      docs_to_resolve,
      env.index(),
      env.lib,
  )

  json_options = syside.JsonStringOptions()
  json_options.include_cross_ref_uris = False
  json_options.indent = False

  options = syside.SerializationOptions().minimal().with_options(
      use_standard_names=True,
      include_derived=True,
      include_redefined=True,
      include_default=False,
      include_optional=False,
      include_implied=True,
  )
  options.fail_action = syside.FailAction.Ignore

  full_documents = []
  for deserialized_model, _report in deserialized_results:
      writer = syside.JsonStringWriter(json_options)
      with deserialized_model.document.mutex.lock() as locked_document:
          syside.serialize(locked_document.root_node, writer, options)
      full_documents.append(json.loads(writer.result))

  return full_documents

```

```auto
For JSON -> text reconstruction:

import syside

def json_to_sysml_text(json_string):
_project_model, deserialized_results = syside.json.loads(
[("memory:///import.sysml", json_string)]
)
deserialized_model, _report = deserialized_results[0]

```

```auto
  env = syside.Environment.get_default()
  id_map = syside.IdMap()

  for mutex in env.documents:
      with mutex.lock() as dep:
          id_map.insert_or_assign(dep)

  with deserialized_model.document.mutex.lock() as locked_document:
      id_map.insert_or_assign(locked_document)

  deserialized_model.link(id_map)

  syside.Sema().resolve(
      [deserialized_model.document],
      env.index(),
      env.lib,
  )

  printer_cfg = syside.PrinterConfig(line_width=80, tab_width=2)
  printer = syside.ModelPrinter.sysml()
  return syside.pprint(deserialized_model.document.root_node, printer, printer_cfg)

```

Is there a newer recommended reconstruction path for achieving byte-equivalent full JSON, or should these remaining cases still be considered known limitations of minimal JSON round-  
trip?

Thanks,

Robert

[2\_FlashlightStarterModel.sysml](https://forum.sensmetry.com/uploads/short-url/etU3NyY1q2GlMw5LdLhRXlcTJr8.sysml) (7.9 KB)

[1\_FlashlightContextClassExercise.sysml](https://forum.sensmetry.com/uploads/short-url/hsNozx7Sawmi1St3rbHbuDURq4E.sysml) (10.6 KB)

[3\_GeneralConcepts.sysml](https://forum.sensmetry.com/uploads/short-url/Auif9YMyN9ZQz99B1s9e1lgp4dk.sysml) (1.1 KB)

[Flashlight.sysml](https://forum.sensmetry.com/uploads/short-url/17E29Rf9Iwe5YUNlaJJXSFpq6co.sysml) (19.6 KB)

[Test7.sysml](https://forum.sensmetry.com/uploads/short-url/wCgca5wy5pxbPlrDbonY2cgdart.sysml) (72.7 KB)

[Test5.sysml](https://forum.sensmetry.com/uploads/short-url/wCgca5wy5pxbPlrDbonY2cgdart.sysml) (72.7 KB)

---

<div class="post-metadata">

**Author:** ![Daumantas](https://yyz2.discourse-cdn.com/flex008/user_avatar/forum.sensmetry.com/daumantas/32/19_2.png) [@Daumantas](https://forum.sensmetry.com/u/Daumantas)\
**Post date:** [July 7, 2026, 8:47am UTC](https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505/4 "2026-07-07T08:47:15Z")

</div>

> [@Robert\_Karban](#):
>
> - metadata-feature-annotated-element: Metadata feature cannot annotate Usage

Printer does not insert `connection` keyword so the element becomes a `Usage` instead.

> [@Robert\_Karban](#):
>
> - incomplete semantic metadata / unresolved baseType diagnostics

Sema on synthetic elements (no source) does not eagerly resolve metadata elements.

I will have a look into both of those issues.

---

<div class="post-metadata">

**Author:** ![Daumantas](https://yyz2.discourse-cdn.com/flex008/user_avatar/forum.sensmetry.com/daumantas/32/19_2.png) [@Daumantas](https://forum.sensmetry.com/u/Daumantas)\
**Post date:** [July 10, 2026, 11:38am UTC](https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505/5 "2026-07-10T11:38:10Z")

</div>

Fixed for the next release.

---

<div class="post-metadata">

**Author:** ![Robert\_Karban](https://yyz2.discourse-cdn.com/flex008/user_avatar/forum.sensmetry.com/robert_karban/32/119_2.png) [@Robert\_Karban](https://forum.sensmetry.com/u/Robert_Karban)\
**Post date:** [July 23, 2026, 11:00pm UTC](https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505/6 "2026-07-23T23:00:23Z")

</div>

Hi,

We are now seeing this on Syside 0.10.3.

This appears related to the same family of issues discussed in:

- Json serialization/deserialization is not symmetric ( [Json serialization/deserialization is not symmetric](https://forum.sensmetry.com/t/json-serialization-deserialization-is-not-symmetric/234) )

- Minimal JSON round-trip with external/library references produces unresolved-reference warnings and non-identical derived relationships after reconstruction

Our current setup:

- We use external/user libraries that are installed separately from the main model.
- For text → JSON, we provide those installed library models as an auxiliary environment.
- On Syside 0.10.3, that makes serialization/commit work correctly.

However, for JSON → text, we still see failures unless we explicitly rebuild and reapply the same auxiliary environment during deserialization/linking/resolution.

Observed failure:

- opening a committed model as textual SysML fails with
  - RuntimeError: NamespaceImport is missing target reference

What fixes it on our side:

1. load installed library models textually
2. build an environment from them
3. use project-style syside.json.loads([…])
4. populate IdMap with both environment docs and deserialized docs
5. call link(…)
6. run syside.Sema().resolve(…, env.index(), env.lib)
7. only then pretty-print to SysML text

So the question is:

On Syside 0.10.3, is this now the expected/supported pattern for JSON→SysML reconstruction when external user libraries are involved?

In other words, should callers always expect to explicitly reconstruct and inject the external-library environment for JSON-\>text, even if those referenced library elements  
are otherwise available in Syside’s broader environment?

This seems consistent with topic 505’s guidance that json.loads(…) alone is not sufficient for external references, but I want to confirm whether this is the intended API  
contract in 0.10.3 rather than just a workaround.

If useful, I can provide a minimal reproducer with:

- one user library package
- one model importing it
- successful text → JSON
- failing JSON → text without auxiliary environment
- successful JSON → text after explicit environment load + link(…) + Sema.resolve(…)

---

<div class="post-metadata">

**Author:** ![Daumantas](https://yyz2.discourse-cdn.com/flex008/user_avatar/forum.sensmetry.com/daumantas/32/19_2.png) [@Daumantas](https://forum.sensmetry.com/u/Daumantas)\
**Post date:** [August 3, 2026, 10:16am UTC](https://forum.sensmetry.com/t/minimal-json-round-trip-with-external-library-references-produces-unresolved-reference-warnings-and-non-identical-derived-relationships-after-reconstruction/505/7 "2026-08-03T10:16:59Z")

</div>

> [@Robert\_Karban](#):
>
> This seems consistent with topic 505’s guidance that json.loads(…) alone is not sufficient for external references, but I want to confirm whether this is the intended API  
> contract in 0.10.3 rather than just a workaround.

Yes, this was always the case.

JSON does not contain source tokens, and we cannot reconstruct references without either source tokens that were used to refer to them (not in JSON), or resolved reference (requires linking).

> [@Robert\_Karban](#):
>
> 1. use project-style syside.json.loads([…])
> 2. populate IdMap with both environment docs and deserialized docs
> 3. call link(…)
> 4. run syside.Sema().resolve(…, env.index(), env.lib)

`syside.json.loads` accepts `environment` and `resolve` callable so 4. and 5. are not required. You can also pass the constructed `IdMap` through `resolve`, especially if it needs to be reused.

6 is not strictly required if JSON already contains implied relationships. In that case, sema will mainly compute inferred expression result types, and potentially a few other properties, which are relevant if the model will go through validation later.
