Estou trabalhando com resultados de simulação armazenados em uma estrutura defaultdict profundamente aninhada. Essa estrutura (data_to_save) mistura tipos padrão do Python (listas, ints, floats, None) com arrays NumPy.
Preciso salvar toda esta estrutura data_to_save em um arquivo JSON. Como json.dump não consegue lidar com arrays NumPy nativamente, e estou usando o NumPy 2.0 (onde os aliases de tipo mais antigos foram removidos), escrevi uma função recursiva personalizada, make_json_serializable, para converter tipos NumPy e lidar com possíveis problemas como np.nan:
import numpy as np
import json
from collections import defaultdict
# Custom converter for NumPy types and NaNs
def make_json_serializable(obj):
if isinstance(obj, np.ndarray): return obj.tolist()
# Important: Handle defaultdict specifically before dict
if isinstance(obj, defaultdict): return {k: make_json_serializable(v) for k, v in obj.items()}
if isinstance(obj, dict): return {k: make_json_serializable(v) for k, v in obj.items()}
if isinstance(obj, list): return [make_json_serializable(i) for i in obj]
# Check base Python types first
if isinstance(obj, (int, float, bool, complex, str, type(None))): return obj
# Check abstract NumPy types (NumPy 2.0+)
if isinstance(obj, np.integer): return int(obj)
if isinstance(obj, np.floating): return float(obj) if not np.isnan(obj) else None # Convert NaN to None for JSON compatibility
if isinstance(obj, np.complexfloating): return {'real': float(obj.real), 'imag': float(obj.imag)}
if isinstance(obj, np.bool_): return bool(obj)
if isinstance(obj, (np.void)): return None
# Fallback if type not recognized
# print(f"Warning: Type {type(obj)} not explicitly handled. Passing as is.") # Optional debug
return obj
# --- END CONVERTER ---
# Example of the nested structure (simplified)
data_to_save = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: defaultdict(dict))))
data_to_save['category_A']['size_X']['config_1']['setting_alpha']['parameter_grid'] = np.linspace(0, 1, 5)
data_to_save['category_A']['size_X']['config_1']['setting_alpha']['metric_array_1'] = np.array([0.1, 0.2, 0.5, 0.8, 1.0])
data_to_save['category_A']['size_X']['config_1']['setting_alpha']['num_valid_entries'] = 5
data_to_save['category_A']['size_X']['config_1']['setting_alpha']['distribution_data'] = {
'0.5': {'size_values': [1, 2, 3], 'normalized_counts': np.array([0.5, 0.3, 0.2]), 'source_count': 5}
}
# ... structure can be deeply nested with more arrays ...
Minha tentativa de salvar esses dados resulta em um TypeError:
# Current (failing) saving code:
output_filename = "output_data.json"
try:
# PROBLEM: This tries to dump the raw structure with NumPy arrays using the standard encoder
temp_dict_structure = json.loads(json.dumps(data_to_save)) # <-- FAILS HERE with TypeError
# The custom converter is intended to run here, but never gets called due to the previous line's error
serializable_data = make_json_serializable(temp_dict_structure)
with open(output_filename, "w") as f:
json.dump(serializable_data, f, indent=2, allow_nan=False) # NaNs should be None by converter
print(f"Data saved to: {output_filename}")
except TypeError as e_json:
print(f"ERROR during JSON processing: {e_json}") # Output: ERROR during JSON processing: Object of type ndarray is not JSON serializable
except Exception as e_save:
print(f"ERROR saving file: {e_save}")
A mensagem de erro TypeError: Object of type ndarray is not JSON serializable aponta para a chamada json.dumps(data_to_save). Isso confirma que o codificador JSON padrão encontra um array NumPy antes que minha função make_json_serializable tenha a chance de convertê-lo. O comando intermediário json.loads(json.dumps(...)) provavelmente foi uma tentativa de converter defaultdicts, mas ele aciona o TypeError primeiro.
Pergunta:
Como posso aplicar corretamente minha função de conversão personalizada (make_json_serializable) a toda a estrutura defaultdict aninhada (data_to_save) antes de passá-la para json.dump? O objetivo é garantir que todos os arrays NumPy aninhados e tipos numéricos especiais sejam convertidos em listas básicas do Python, ints, floats ou None, resolvendo o TypeError.