Description
When ops.Relation.load() is used to load from unit data into a data class, a json.decoder.JSONDecodeError: Extra data is raised.
I believe this is due to the default json.loads() decoder failing to interpret the standard values in the unit data, e.g. {'egress-subnets': '10.200.245.189/32', 'ingress-address': '10.200.245.189', 'private-address': '10.200.245.189'}.
To reproduce
The error can be reproduced by this charm code, which save()s then load()s unit data in a peer relation:
class PeerData(pydantic.BaseModel):
myflag: bool = False
class ReloadIssueCharm(ops.CharmBase):
"""Charm the application."""
def __init__(self, framework: ops.Framework):
super().__init__(framework)
framework.observe(self.on.start, self._on_start)
def _on_start(self, _: ops.StartEvent):
if peers := self.model.get_relation("peers"):
save_data = PeerData(myflag=True)
peers.save(save_data, self.unit)
logger.info("Saved peer data: %s", save_data)
load_data = peers.load(PeerData, self.unit)
logger.info("Loaded peer data: %s", load_data)
The _on_start handler fails at peers.load(PeerData, self.unit):
`json.decoder.JSONDecodeError: Extra data` traceback
2026-07-01 09:58:38 INFO unit.reload-issue-charm/0.juju-log server.go:405 Saved peer data: myflag=True
2026-07-01 09:58:38 ERROR unit.reload-issue-charm/0.juju-log server.go:405 Uncaught exception while in charm code:
Traceback (most recent call last):
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/src/charm.py", line 39, in <module>
ops.main(ReloadIssueCharm)
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/venv/lib/python3.12/site-packages/ops/__init__.py", line 360, in __call__
return _main.main(charm_class=charm_class, use_juju_for_storage=use_juju_for_storage)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/venv/lib/python3.12/site-packages/ops/_main.py", line 507, in main
manager.run()
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/venv/lib/python3.12/site-packages/ops/_main.py", line 491, in run
self._emit()
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/venv/lib/python3.12/site-packages/ops/_main.py", line 426, in _emit
self._emit_charm_event(self.dispatcher.event_name)
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/venv/lib/python3.12/site-packages/ops/_main.py", line 470, in _emit_charm_event
event_to_emit.emit(*args, **kwargs)
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/venv/lib/python3.12/site-packages/ops/framework.py", line 337, in emit
framework._emit(event)
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/venv/lib/python3.12/site-packages/ops/framework.py", line 910, in _emit
self._reemit(event_path)
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/venv/lib/python3.12/site-packages/ops/framework.py", line 1028, in _reemit
custom_handler(event)
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/src/charm.py", line 32, in _on_start
load_data = peers.load(PeerData, self.unit)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/lib/juju/agents/unit-reload-issue-charm-0/charm/venv/lib/python3.12/site-packages/ops/model.py", line 1829, in load
value = decoder(value)
^^^^^^^^^^^^^^
File "/usr/lib/python3.12/json/__init__.py", line 346, in loads
return _default_decoder.decode(s)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/json/decoder.py", line 340, in decode
raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 7 (char 6)
Or, more immediately:
>>> json.loads('10.200.245.189')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.12/json/__init__.py", line 346, in loads
return _default_decoder.decode(s)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.12/json/decoder.py", line 340, in decode
raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 7 (char 6)
Workaround
We are working around this with a custom decoder that interprets the values as strings:
def _unit_data_decoder(value: str) -> str:
if not (value.startswith('"') and value.endswith('"')):
value = f'"{value}"'
return json.loads(value)
E.g. equivalent to json.loads('"10.200.245.189"').
Then load_data = peers.load(PeerData, self.unit, decoder=_unit_data_decoder) runs as expected:
2026-07-01 10:01:29 INFO unit.reload-issue-charm-workaround/0.juju-log server.go:405 Saved peer data: myflag=True
2026-07-01 10:01:29 INFO unit.reload-issue-charm-workaround/0.juju-log server.go:405 Loaded peer data: myflag=True
Expectation
ops.Relation.load() should be able to read unit data without without raising a JSONDecodeError and without need for a custom decoder.
Description
When
ops.Relation.load()is used to load from unit data into a data class, ajson.decoder.JSONDecodeError: Extra datais raised.I believe this is due to the default
json.loads()decoder failing to interpret the standard values in the unit data, e.g.{'egress-subnets': '10.200.245.189/32', 'ingress-address': '10.200.245.189', 'private-address': '10.200.245.189'}.To reproduce
The error can be reproduced by this charm code, which
save()s thenload()s unit data in a peer relation:The
_on_starthandler fails atpeers.load(PeerData, self.unit):`json.decoder.JSONDecodeError: Extra data` traceback
Or, more immediately:
Workaround
We are working around this with a custom decoder that interprets the values as strings:
E.g. equivalent to
json.loads('"10.200.245.189"').Then
load_data = peers.load(PeerData, self.unit, decoder=_unit_data_decoder)runs as expected:Expectation
ops.Relation.load()should be able to read unit data without without raising aJSONDecodeErrorand without need for a custom decoder.