From 24c1450c4cb523cc735fb2ef22a922aebadb6b7e Mon Sep 17 00:00:00 2001 From: HowToSD Date: Sun, 16 Feb 2025 18:44:51 -0800 Subject: [PATCH] Added Pandas Load JSON and Pandas Save JSON nodes. --- __init__.py | 6 +++ docs/ug.md | 5 ++ modules/pandas_wrapper/pandas_load_json.py | 46 ++++++++++++++++ modules/pandas_wrapper/pandas_save_json.py | 63 ++++++++++++++++++++++ pyproject.toml | 2 +- web/js/SaveJSON.js | 36 +++++++++++++ 6 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 modules/pandas_wrapper/pandas_load_json.py create mode 100644 modules/pandas_wrapper/pandas_save_json.py create mode 100644 web/js/SaveJSON.js diff --git a/__init__.py b/__init__.py index 5771209..a9170bb 100644 --- a/__init__.py +++ b/__init__.py @@ -15,9 +15,11 @@ from .modules.pandas_wrapper.pandas_join import PandasJoin from .modules.pandas_wrapper.pandas_load_csv import PandasLoadCSV from .modules.pandas_wrapper.pandas_load_csv_with_encoding import PandasLoadCSVWithEncoding from .modules.pandas_wrapper.pandas_load_csv_with_index import PandasLoadCSVWithIndex +from .modules.pandas_wrapper.pandas_load_json import PandasLoadJSON from .modules.pandas_wrapper.pandas_loc_cell_str import PandasLocCellStr from .modules.pandas_wrapper.pandas_loc_row_series import PandasLocRowSeries from .modules.pandas_wrapper.pandas_save_csv import PandasSaveCSV +from .modules.pandas_wrapper.pandas_save_json import PandasSaveJSON from .modules.pandas_wrapper.pandas_select_columns import PandasSelectColumns from .modules.pandas_wrapper.pandas_select_rows import PandasSelectRows from .modules.pandas_wrapper.pandas_series_to_string import PandasSeriesToString @@ -46,9 +48,11 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "PandasLoadCSV": PandasLoadCSV, "PandasLoadCSVWithEncoding": PandasLoadCSVWithEncoding, "PandasLoadCSVWithIndex": PandasLoadCSVWithIndex, + "PandasLoadJSON": PandasLoadJSON, "PandasLocCellStr": PandasLocCellStr, "PandasLocRowSeries": PandasLocRowSeries, "PandasSaveCSV": PandasSaveCSV, + "PandasSaveJSON": PandasSaveJSON, "PandasSelectColumns": PandasSelectColumns, "PandasSelectRows": PandasSelectRows, "PandasSeriesToString": PandasSeriesToString, @@ -80,9 +84,11 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "PandasLoadCSV": "Pandas Load CSV", "PandasLoadCSVWithEncoding": "Pandas Load CSV With Encoding", "PandasLoadCSVWithIndex": "Pandas Load CSV With Index", + "PandasLoadJSON": "Pandas Load JSON", "PandasLocCellStr": "Pandas Loc Cell Str", "PandasLocRowSeries": "Pandas Loc Row Series", "PandasSaveCSV": "Pandas Save CSV", + "PandasSaveJSON": "Pandas Save JSON", "PandasSelectColumns": "Pandas Select Columns", "PandasSelectRows": "Pandas Select Rows", "PandasSeriesToString": "Pandas Series To String", diff --git a/docs/ug.md b/docs/ug.md index 746624d..341d273 100644 --- a/docs/ug.md +++ b/docs/ug.md @@ -18,7 +18,9 @@ You can right-click, select **Add Node**, go to **Data Analysis**, and look for | **Pandas Load CSV** | Load a CSV file | | **Pandas Load CSV With Encoding** | Load a CSV file not encoded in utf-8 | | **Pandas Load CSV With Index** | Load a CSV file containing index | +| **Pandas Load JSON** | Load a JSON file | | **Pandas Save CSV** | Save a DataFrame to a CSV file | +| **Pandas Save JSON** | Save a DataFrame to a JSON file | | **Pandas Create** | Create a DataFrame from CSV text field | | **Pandas Create With Index** | Create a DataFrame from CSV text field containing index | | **CDA JSON Create** | Create JSON from a string entered in a text field | @@ -71,11 +73,14 @@ The examples directory contains workflows that load data from an example dataset Use **Pandas Load CSV** node to load a CSV, or **Pandas Load CSV With Index** node if the CSV contains an index column. These nodes assume that the file is encoded in utf-8. If the file is not encoded in utf-8, use **Pandas Load CSV With Encoding** to specify the encoding of the file. This is necessary if the data was collected before utf-8 encoding became popular. For example, you can try "latin_1" for a file that contains accented characters in the US and Western Europe. ![Pandas Load CSV node](images/load.jpg) +Use **Pandas Load JSON** node to load a JSON file. + These nodes emit the DataFrame in a JSON string. CSV file path is relative to the ComfyUI installation directory unless you specify the absolute file path. ## Saving data Use **Pandas Save CSV** node to save the DataFrame to a CSV file. +Use **Pandas Save JSON** node to save the DataFrame to a JSON file. ## Creating data on the UI To construct a DataFrame on the UI, use **Pandas Create** or **Pandas Create with Index** nodes. These nodes display the multiline text area where you can enter the data in CSV format. diff --git a/modules/pandas_wrapper/pandas_load_json.py b/modules/pandas_wrapper/pandas_load_json.py new file mode 100644 index 0000000..a96cc33 --- /dev/null +++ b/modules/pandas_wrapper/pandas_load_json.py @@ -0,0 +1,46 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd + + +class PandasLoadJSON: + """ + PandasLoadJSON: + A class for loading JSON files into a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `load_json` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "file_path": ("STRING", {"multiline": False}), + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "load_json" + CATEGORY: str = "Data Analysis" + + def load_json(self, file_path: str) -> tuple: + """ + Loads a JSON file into a pandas DataFrame and converts it to a JSON string. + + Args: + file_path (str): The path to the JSON file. + + Returns: + tuple: A tuple containing a JSON string representation of the DataFrame. + """ + # Read JSON file into DataFrame + df = pd.read_json(file_path) + + # Convert DataFrame to JSON + result_json = df.to_json() + + return (result_json,) diff --git a/modules/pandas_wrapper/pandas_save_json.py b/modules/pandas_wrapper/pandas_save_json.py new file mode 100644 index 0000000..691938d --- /dev/null +++ b/modules/pandas_wrapper/pandas_save_json.py @@ -0,0 +1,63 @@ +""" +This code is based on +https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/py/show_text.py + +See credit/credit.md for the full license. +""" +from typing import Any, Dict +from io import StringIO +import pandas as pd + +class PandasSaveJSON: + """ + PandasSaveJSON: + A class for saving a PandasDataFrame to a JSON file. + """ + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "dataframe_json": ("STRING", {"forceInput": True}), + "file_path": ("STRING", {"multiline": False}) + }, + "hidden": { + "unique_id": "UNIQUE_ID", + "extra_pnginfo": "EXTRA_PNGINFO", + }, + } + + INPUT_IS_LIST = True + RETURN_TYPES = ("STRING",) + FUNCTION = "save_json" + OUTPUT_NODE = True + OUTPUT_IS_LIST = (True,) + + CATEGORY = "Data Analysis" + + def save_json(self, dataframe_json,file_path=None, unique_id=None, extra_pnginfo=None): + text = None + if unique_id is not None and extra_pnginfo is not None: + if not isinstance(extra_pnginfo, list): + print("Error: extra_pnginfo is not a list") + elif ( + not isinstance(extra_pnginfo[0], dict) + or "workflow" not in extra_pnginfo[0] + ): + print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key") + elif file_path is None or len(file_path) == 0: + print("Error: file path is not specified") + else: + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json[0])) + df.to_json(path_or_buf=file_path[0]) # Save as JSON + text = [str(df)] # wrap in list + workflow = extra_pnginfo[0]["workflow"] + node = next( + (x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])), + None, + ) + if node: + node["widgets_values"] = [text] + else: + print("unique_id or extra_pnginfo is None.") + return {"ui": {"text": text}, "result": (text,)} diff --git a/pyproject.toml b/pyproject.toml index be890a1..ec6259a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "data-analysis" description = "Data analysis custom modules for ComfyUI - Use Pandas & Matplotlib within ComfyUI" -version = "1.0.16" +version = "1.0.17" license = {file = "LICENSE"} [project.urls] diff --git a/web/js/SaveJSON.js b/web/js/SaveJSON.js new file mode 100644 index 0000000..e89f4ca --- /dev/null +++ b/web/js/SaveJSON.js @@ -0,0 +1,36 @@ +/* +This code is based on +https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/web/js/showText.js + +See credit/credit.md for the full license. +*/ + +import { app } from "../../../scripts/app.js"; +import { ComfyWidgets } from "../../../scripts/widgets.js"; + +// Displays input text on a node +app.registerExtension({ + name: "HowToSD.SaveJSON", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "PandasSaveJSON") { // This needs to match NODE_CLASS_MAPPINGS in Python + function populate(text) { + // No op as we are not updating the node's UI for now. + } + + // When the node is executed we will be sent the input text, display this in the widget + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = function (message) { + onExecuted?.apply(this, arguments); + populate.call(this, message.text); + }; + + const onConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = function () { + onConfigure?.apply(this, arguments); + if (this.widgets_values?.length) { + populate.call(this, this.widgets_values.slice(+this.widgets_values.length > 1)); + } + }; + } + }, +}); \ No newline at end of file