Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/power_grid_model_ds/_core/model/grids/serialization/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import dataclasses
import json
import logging
import math
from pathlib import Path
from typing import TYPE_CHECKING, Any

Expand Down Expand Up @@ -64,7 +65,7 @@ def serialize_to_dict[G: Grid](grid: G, strict: bool = True, **kwargs) -> dict:
if _is_serializable(field_value, strict, **kwargs):
serialized_data[field.name] = field_value

return {"data": serialized_data}
return {"data": _replace_nan_with_none(serialized_data)}


def deserialize_from_json[G: Grid](path: Path, target_grid_class: type[G]) -> G:
Expand Down Expand Up @@ -182,3 +183,14 @@ def _is_serializable(value: Any, strict: bool, **kwargs) -> bool:
_logger.warning(msg)
return False
return True


def _replace_nan_with_none(value: Any) -> Any:
"""Recursively replace NaN values with JSON-compatible null values."""
if isinstance(value, float) and math.isnan(value):
return None
if isinstance(value, dict):
return {key: _replace_nan_with_none(item) for key, item in value.items()}
if isinstance(value, list):
return [_replace_nan_with_none(item) for item in value]
return value
10 changes: 10 additions & 0 deletions tests/unit/model/grids/serialization/data/legacy_nan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"data": {
"node": [
{
"id": 1,
"u_rated": NaN
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: Contributors to the Power Grid Model project <powergridmodel@lfenergy.org>

SPDX-License-Identifier: MPL-2.0
19 changes: 19 additions & 0 deletions tests/unit/model/grids/serialization/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,25 @@ def test_pgm_roundtrip(self, request, grid_fixture: str, tmp_path: Path):

assert array_equal_with_nan(original_array, loaded_array), f"Array '{array_name}' does not match"

def test_nan_serializes_as_json_null(self, basic_grid: Grid, tmp_path: Path):
basic_grid.node.u_rated[0] = np.nan

path = basic_grid.serialize(tmp_path / "grid.json")
with path.open(encoding="utf-8") as file:
file_data = json.load(file)

string_data = json.loads(basic_grid.serialize(mode="json_string"))

assert file_data["data"]["node"][0]["u_rated"] is None
assert string_data["data"]["node"][0]["u_rated"] is None

def test_deserialize_legacy_nan_json(self):
path = Path(__file__).parent / "data" / "legacy_nan.json"

grid = Grid.deserialize(path)

assert np.isnan(grid.node.u_rated[0])


class TestCrossTypeCompatibility:
"""Test cross-type loading and compatibility"""
Expand Down