Skip to content

Conversation

@jaapschoutenalliander
Copy link
Member

@jaapschoutenalliander jaapschoutenalliander commented Oct 13, 2025

This pull request introduces robust JSON serialization and deserialization support for Grid objects, enabling interoperability and extension handling in the power grid modeling library. It adds new serialization utilities, integrates them into the Grid API, and provides comprehensive unit tests to ensure correct roundtrips and compatibility across extended types

Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
@Thijss
Copy link
Member

Thijss commented Oct 22, 2025

Should we expose these functions through the grid or as seperate functions (current draft)?

I'd vote for grid.serialize(path) and grid = Grid.deserialize(path)


try:
array_field = grid.find_array_field(getattr(grid, attr_name).__class__)
matched_columns = {
Copy link
Member

@Thijss Thijss Oct 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we can re-use PowerGridModelInterface._match_dtypes here (will have to move the code)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, that could work. However, that function compares two dtype objects and here we have one dtype object and a dict with columns as keys. So they do not match completely. Given the low complexity building a shared function might not be worthwile

return path


def load_grid_from_json(path: Path, target_grid_class=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps just:

Suggested change
def load_grid_from_json(path: Path, target_grid_class=None):
def deserialize(path: Path, target_grid_class=None):

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now that would work, but if we later add another serialisation option having load_grid_from... works better

logger.warning(f"Failed to restore '{attr_name}': {e}")


def save_grid_to_json(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps just

Suggested change
def save_grid_to_json(
def serialize(

if isinstance(field_value, (int, float, str, bool)):
serialized_data[field.name] = field_value

if not isinstance(field_value, FancyArray) or field_value.size == 0:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would crash here if not FancyArray for now instead of skipping. Or maybe this should be an option to skip?

continue

if not issubclass(getattr(grid, attr_name).__class__, FancyArray):
setattr(grid, attr_name, attr_values)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check that it is the correct type as well?
Now you could set a bool for something that is typed as a string.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now get the type from the grid and make sure to cast it to the expected type (if possible). I will also add test for sad flow

Comment on lines 103 to 105
assert loaded_grid.node.size == basic_grid.node.size
assert loaded_grid.line.size == basic_grid.line.size
assert list(loaded_grid.node.id) == list(basic_grid.node.id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cant we just use, with assert_array_equal from numpy.testing:
(also applicable to other tests in this file)

Suggested change
assert loaded_grid.node.size == basic_grid.node.size
assert loaded_grid.line.size == basic_grid.line.size
assert list(loaded_grid.node.id) == list(basic_grid.node.id)
assert_array_equal(loaded_grid.node.data, basic_grid.node.data)
assert_array_equal(loaded_grid.line.data, basic_grid.line.data)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated to use the array_equal from pgm_ds where applicable

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ik mis tests voor wanneer het crasht.

jaapschoutenalliander and others added 5 commits October 22, 2025 18:14
Co-authored-by: Vincent Koppen <[email protected]>
Signed-off-by: Jaap Schouten <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
Signed-off-by: jaapschoutenalliander <[email protected]>
@sonarqubecloud
Copy link

@jaapschoutenalliander jaapschoutenalliander marked this pull request as ready for review October 22, 2025 17:29
txt_lines = f.readlines()
return TextSource(grid_class=cls).load_from_txt(*txt_lines)

def to_json(self, path: Path, **kwargs) -> Path:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this way, we'll need two extra methods for each serialization method we add in the future. I'd rather name this method serialize and add a method parameter once we include other serialization methods.

future example:

def serialize(self, path: Path, method: Literal["json", "msgpack", "parquet"], **kwargs):

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants