{"record":{"id":"73ef980b7d07eb34","repo":"mlflow/mlflow","slug":"failed-to-save-data-as-table-as-the-data-is-not","errorCode":null,"errorMessage":"Failed to save {data} as table as the data is not JSON serializable. Error: {e}","messagePattern":"Failed to save (.+?) as table as the data is not JSON serializable\\. Error: (.+?)","errorType":"exception","errorClass":"MlflowException","httpStatus":null,"severity":"error","filePath":"mlflow/tracking/client.py","lineNumber":3507,"sourceCode":"        artifact_dir = None if artifact_dir == \"\" else artifact_dir\n        artifacts = [f.path for f in self.list_artifacts(run_id, path=artifact_dir)]\n        if artifact_file in artifacts:\n            with tempfile.TemporaryDirectory() as tmpdir:\n                downloaded_artifact_path = self.download_artifacts(\n                    run_id=run_id, path=artifact_file, dst_path=tmpdir\n                )\n                existing_predictions = self._read_from_file(downloaded_artifact_path)\n            data = pd.concat([existing_predictions, data], ignore_index=True)\n            _logger.debug(\n                \"Appending new table to already existing artifact \"\n                f\"{artifact_file} for run {run_id}.\"\n            )\n\n        with self._log_artifact_helper(run_id, artifact_file) as artifact_path:\n            try:\n                write_to_file(data, artifact_path)\n            except Exception as e:\n                raise MlflowException(\n                    f\"Failed to save {data} as table as the data is not JSON serializable. \"\n                    f\"Error: {e}\"\n                )\n\n        run = self.get_run(run_id)\n\n        # Get the current value of the tag\n        current_tag_value = json.loads(run.data.tags.get(MLFLOW_LOGGED_ARTIFACTS, \"[]\"))\n        tag_value = {\"path\": artifact_file, \"type\": \"table\"}\n\n        # Append the new tag value to the list if one doesn't exists\n        if tag_value not in current_tag_value:\n            current_tag_value.append(tag_value)\n            # Set the tag with the updated list\n            self.set_tag(run_id, MLFLOW_LOGGED_ARTIFACTS, json.dumps(current_tag_value))\n\n    def load_table(\n        self,","sourceCodeStart":3489,"sourceCodeEnd":3525,"githubUrl":"https://github.com/mlflow/mlflow/blob/6a27f2decc0b76eb1b54af31849784addb357dbc/mlflow/tracking/client.py#L3489-L3525","documentation":"log_table writes the DataFrame/dict via write_to_file; JSON mode uses pandas' to_json which requires JSON-serializable content (Parquet has its own type constraints). If writing throws for any reason (unserializable objects like numpy scalars, datetimes with timezones in JSON mode, custom objects, or any underlying write error), the exception is wrapped in an MlflowException with this message.","triggerScenarios":"Calling client.log_table with a DataFrame/dict containing non-JSON-serializable values (numpy types, sets, custom objects, bytes in a .json artifact, mixed-type object columns), where artifact_file ends in '.json' so to_json is used; any parquet write failure (e.g. unsupported column type) is also caught by this handler.","commonSituations":"Logging numpy.int64/float64 scalars or datetime64 columns into a .json table; embedding custom objects (e.g. model handles, image objects in unexpected places) in cells; dict data whose conversion produced dtype='object' columns with heterogeneous values.","solutions":["Sanitize the DataFrame before logging: convert numpy scalars with .item() and datetimes with df.astype(str) or isoformat strings.","Log as '.parquet' instead of '.json' to leverage Parquet's richer native type support (numpy types, datetimes).","Use df = df.convert_dtypes() or apply a json-normalization pass (pandas.io.json.json_normalize / custom clean function) to coerce object columns to primitives.","Inspect the inner Error: {e} text of the MlflowException to identify the exact offending value/column."],"exampleFix":"// before\nimport numpy as np\ndf = pd.DataFrame({\"score\": [np.float64(0.9)], \"created\": [pd.Timestamp.now()]})\nclient.log_table(run_id, df, \"table.json\")  # MlflowException\n\n// after\ndf = df.convert_dtypes()\ndf[\"created\"] = df[\"created\"].astype(str)\nclient.log_table(run_id, df, \"table.parquet\")","handlingStrategy":"try-catch","validationCode":"import pandas as pd\n\ndef make_json_safe(df: pd.DataFrame) -> pd.DataFrame:\n    df = df.copy()\n    for col in df.columns:\n        df[col] = df[col].map(lambda x: x.item() if hasattr(x, \"item\") and callable(getattr(x, \"item\")) else x)\n        try:\n            pd.io.json.dumps(df[col].head(1).tolist())\n        except TypeError:\n            df[col] = df[col].astype(str)\n    return df\n\nclient.log_table(run_id, make_json_safe(df), \"table.json\")","typeGuard":"def is_json_serializable(value) -> bool:\n    import json\n    try:\n        json.dumps(value)\n        return True\n    except (TypeError, ValueError):\n        return False","tryCatchPattern":"try:\n    client.log_table(run_id, df, \"table.json\")\nexcept MlflowException as e:\n    if \"not JSON serializable\" in str(e):\n        client.log_table(run_id, sanitize(df), \"table.parquet\")  # richer types via parquet\n    else:\n        raise","preventionTips":["Prefer .parquet for tables containing numpy types, timestamps, or nested values — it avoids JSON serialization limits entirely.","Coerce object columns with df.convert_dtypes() and cast datetimes to strings before JSON logging.","Include the exception message's inner Error detail in your logging to quickly locate the offending column/value."],"tags":["mlflow","serialization","json","pandas","dataframe"],"backgroundTag":"json-serialization-failed","analyzedSha":"6a27f2decc0b76eb1b54af31849784addb357dbc","analyzedAt":"2026-08-29T20:54:51.419Z","schemaVersion":2},"datasetVersion":"2026-08-29T22:17:34.462Z"}