@@ -50,6 +50,26 @@ def _load_valid_classes() -> set[str]:
5050 return _valid_classes
5151
5252
53+
54+ def _json_sanitize (obj : Any ) -> Any :
55+ if obj is None or isinstance (obj , (str , int , float , bool )):
56+ return obj
57+ if isinstance (obj , (bytes , bytearray )):
58+ try :
59+ return obj .decode ('utf-8' , errors = 'ignore' )
60+ except Exception :
61+ return str (obj )
62+ if isinstance (obj , dict ):
63+ return {str (k ): _json_sanitize (v ) for k , v in obj .items ()}
64+ if isinstance (obj , (list , tuple , set )):
65+ return [_json_sanitize (x ) for x in obj ]
66+ # Fallback
67+ try :
68+ return str (obj )
69+ except Exception :
70+ return repr (obj )
71+
72+
5373def _start_frontend_dev_if_available ():
5474 """
5575 Try to start the Vite dev server for the React UI. This is best-effort and will
@@ -172,7 +192,15 @@ def list_instruments():
172192 entry : Dict [str , Any ] = {"id" : dev_id }
173193 reg_data = registry .get (dev_id , {})
174194 if 'IDN' in reg_data :
175- entry ['IDN' ] = reg_data ['IDN' ]
195+ idn_val = reg_data ['IDN' ]
196+ try :
197+ if isinstance (idn_val , (bytes , bytearray )):
198+ entry ['IDN' ] = idn_val .decode ('utf-8' , errors = 'ignore' ).strip ()
199+ else :
200+ entry ['IDN' ] = str (idn_val )
201+ except Exception :
202+ # Fallback to repr to ensure JSON-serializable
203+ entry ['IDN' ] = repr (idn_val )
176204
177205 # Attempt to populate classes/channels from manifest based on device driver and model
178206 try :
@@ -229,7 +257,7 @@ def list_instruments():
229257 entry ['classes' ] = classes_list
230258
231259 items .append (entry )
232- return items
260+ return _json_sanitize ( items )
233261
234262
235263@app .get ("/instruments/{klass}/{device_id}" , summary = "Get manifest features for class on device" , response_model = dict )
0 commit comments