<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - json</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/json.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2020-05-07T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>Using Complex Data Sources with PyQt6 Model/View Architecture — How to use JSON, nested data, and other complex structures in your Qt table and list views</title><link href="https://www.pythonguis.com/faq/complex-data-in-pyqt-models/" rel="alternate"/><published>2020-05-07T09:00:00+00:00</published><updated>2020-05-07T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2020-05-07:/faq/complex-data-in-pyqt-models/</id><summary type="html">I have a complex data set in a JSON file. Is it possible to use this as my model's data in PyQt views, or do I need to simplify it to a basic table?</summary><content type="html">
            &lt;blockquote&gt;
&lt;p&gt;I have a complex data set in a JSON file. Is it possible to use this as my model's data in PyQt views, or do I need to simplify it to a basic table?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Great news: you can use &lt;em&gt;any&lt;/em&gt; data structure you like in a PyQt model. JSON files, nested dictionaries, lists of objects, database results &amp;mdash; it all works. The model acts as an interface between how &lt;em&gt;you&lt;/em&gt; store your data and how &lt;em&gt;Qt&lt;/em&gt; expects to see it. As long as your model returns data in the format Qt expects, the view will display it without complaint.&lt;/p&gt;
&lt;p&gt;In this tutorial, we'll walk through how to load a JSON file and display its data in a &lt;code&gt;QTableView&lt;/code&gt; using a custom &lt;code&gt;QAbstractTableModel&lt;/code&gt;. Along the way, you'll see how the model translates between your real-world data and Qt's row-and-column view of the world.&lt;/p&gt;
&lt;h2 id="how-modelview-works-with-complex-data"&gt;How Model/View Works with Complex Data&lt;/h2&gt;
&lt;p&gt;Qt's &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-modelview-architecture/"&gt;Model/View architecture&lt;/a&gt; separates &lt;em&gt;data&lt;/em&gt; from &lt;em&gt;presentation&lt;/em&gt;. The view (e.g. &lt;code&gt;QTableView&lt;/code&gt;, &lt;code&gt;QListView&lt;/code&gt;) asks the model questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;"How many rows are there?"&lt;/li&gt;
&lt;li&gt;"How many columns are there?"&lt;/li&gt;
&lt;li&gt;"What's the data at row 2, column 3?"&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your model answers these questions by looking at whatever underlying data structure you're using. The view doesn't care whether your data lives in a flat list, a JSON file, a pandas DataFrame, or a SQL database. It only cares about the answers.&lt;/p&gt;
&lt;p&gt;This means you can wrap &lt;em&gt;any&lt;/em&gt; data source in a model. You just need to translate between your data's shape and the row/column format that Qt uses.&lt;/p&gt;
&lt;h2 id="a-simple-json-example"&gt;A Simple JSON Example&lt;/h2&gt;
&lt;p&gt;Let's start with a straightforward example. Suppose you have a JSON file containing a list of people, where each person has several fields:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-json"&gt;json&lt;/span&gt;
&lt;pre&gt;&lt;code class="json"&gt;[
    {
        "name": "Alice",
        "age": 34,
        "city": "Berlin",
        "role": "Engineer"
    },
    {
        "name": "Bob",
        "age": 28,
        "city": "London",
        "role": "Designer"
    },
    {
        "name": "Charlie",
        "age": 41,
        "city": "New York",
        "role": "Manager"
    },
    {
        "name": "Diana",
        "age": 25,
        "city": "Tokyo",
        "role": "Developer"
    }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Save this as &lt;code&gt;people.json&lt;/code&gt; in the same directory as your Python script.&lt;/p&gt;
&lt;p&gt;Each object in the list becomes a row in the table, and each field becomes a column. The model's job is to map between these two representations.&lt;/p&gt;
&lt;h2 id="building-the-model"&gt;Building the Model&lt;/h2&gt;
&lt;p&gt;To display this data in a &lt;code&gt;QTableView&lt;/code&gt;, we subclass &lt;code&gt;QAbstractTableModel&lt;/code&gt; and implement three required methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rowCount()&lt;/code&gt; &amp;mdash; returns the number of rows&lt;/li&gt;
&lt;li&gt;&lt;code&gt;columnCount()&lt;/code&gt; &amp;mdash; returns the number of columns&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data()&lt;/code&gt; &amp;mdash; returns the data for a given row and column&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's how that looks:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import json
from PyQt6.QtCore import Qt, QAbstractTableModel


class JsonTableModel(QAbstractTableModel):
    def __init__(self, data, headers=None):
        super().__init__()
        self._data = data
        # Use provided headers, or extract keys from the first item.
        if headers:
            self._headers = headers
        elif data:
            self._headers = list(data[0].keys())
        else:
            self._headers = []

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._headers)

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            row = self._data[index.row()]
            key = self._headers[index.column()]
            return str(row.get(key, ""))
        return None

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                return self._headers[section].capitalize()
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Let's walk through what's happening here.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;__init__&lt;/code&gt; method stores the list of dictionaries and figures out the column headers. If you don't provide headers explicitly, it grabs the keys from the first dictionary in the list.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;rowCount()&lt;/code&gt; method returns the length of the data list &amp;mdash; one row per dictionary.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;columnCount()&lt;/code&gt; method returns the number of headers &amp;mdash; one column per key.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;data()&lt;/code&gt; method is where the translation happens. Qt calls this method with an &lt;code&gt;index&lt;/code&gt; (which contains a row and column number) and a &lt;code&gt;role&lt;/code&gt; (which describes &lt;em&gt;what kind&lt;/em&gt; of data Qt wants). For display purposes, we use &lt;code&gt;Qt.ItemDataRole.DisplayRole&lt;/code&gt;. We look up the right dictionary from the list using the row number, then look up the right value using the column header as a key.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;headerData()&lt;/code&gt; method provides labels for the column headers at the top of the table.&lt;/p&gt;
&lt;h2 id="displaying-the-data"&gt;Displaying the Data&lt;/h2&gt;
&lt;p&gt;Now let's wire everything together in a small application:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
import json

from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView
from PyQt6.QtCore import Qt, QAbstractTableModel


class JsonTableModel(QAbstractTableModel):
    def __init__(self, data, headers=None):
        super().__init__()
        self._data = data
        if headers:
            self._headers = headers
        elif data:
            self._headers = list(data[0].keys())
        else:
            self._headers = []

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._headers)

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            row = self._data[index.row()]
            key = self._headers[index.column()]
            return str(row.get(key, ""))
        return None

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                return self._headers[section].capitalize()
        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("JSON Table Model")

        # Load data from the JSON file.
        with open("people.json", "r") as f:
            data = json.load(f)

        # Create the model and view.
        self.model = JsonTableModel(data)
        self.table = QTableView()
        self.table.setModel(self.model)

        self.setCentralWidget(self.table)
        self.resize(500, 300)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and you'll see a table view displaying the data from your JSON file, complete with column headers.&lt;/p&gt;
&lt;h2 id="working-with-nested-json"&gt;Working with Nested JSON&lt;/h2&gt;
&lt;p&gt;Real-world JSON is often more complex than a flat list of objects. You might have nested structures like this:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-json"&gt;json&lt;/span&gt;
&lt;pre&gt;&lt;code class="json"&gt;[
    {
        "name": "Alice",
        "age": 34,
        "address": {
            "city": "Berlin",
            "country": "Germany"
        },
        "skills": ["Python", "C++"]
    },
    {
        "name": "Bob",
        "age": 28,
        "address": {
            "city": "London",
            "country": "UK"
        },
        "skills": ["JavaScript", "CSS"]
    }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Save this as &lt;code&gt;people_nested.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;QTableView&lt;/code&gt; is inherently a flat, two-dimensional grid. You can't directly display nested structures in it, but you &lt;em&gt;can&lt;/em&gt; flatten the data in your model. The model is the perfect place to do this &amp;mdash; your original data stays complex, but the model presents a simplified view to Qt.&lt;/p&gt;
&lt;p&gt;Here's a model that handles nested dictionaries by using dot-notation column definitions:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;class NestedJsonTableModel(QAbstractTableModel):
    def __init__(self, data, columns):
        super().__init__()
        self._data = data
        # columns is a list of tuples: (header_label, key_path)
        # key_path is a dot-separated string like "address.city"
        self._columns = columns

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._columns)

    def _resolve(self, obj, key_path):
        """Walk into a nested dict using a dot-separated path."""
        keys = key_path.split(".")
        for key in keys:
            if isinstance(obj, dict):
                obj = obj.get(key, "")
            else:
                return ""
        return obj

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            row = self._data[index.row()]
            _, key_path = self._columns[index.column()]
            value = self._resolve(row, key_path)
            # Handle lists by joining them into a string.
            if isinstance(value, list):
                return ", ".join(str(v) for v in value)
            return str(value)
        return None

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                label, _ = self._columns[section]
                return label
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;_resolve&lt;/code&gt; method walks through the nested dictionary using a dot-separated path. So &lt;code&gt;"address.city"&lt;/code&gt; first looks up &lt;code&gt;"address"&lt;/code&gt; (getting a nested dict), then looks up &lt;code&gt;"city"&lt;/code&gt; inside that. This keeps the logic clean and reusable.&lt;/p&gt;
&lt;p&gt;You define the columns you want to display when creating the model:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;columns = [
    ("Name", "name"),
    ("Age", "age"),
    ("City", "address.city"),
    ("Country", "address.country"),
    ("Skills", "skills"),
]

with open("people_nested.json", "r") as f:
    data = json.load(f)

model = NestedJsonTableModel(data, columns)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This approach gives you full control over which parts of your data appear in the table and in what order. Your JSON stays as-is &amp;mdash; the model handles the mapping.&lt;/p&gt;
&lt;h2 id="complete-working-example-with-nested-data"&gt;Complete Working Example with Nested Data&lt;/h2&gt;
&lt;p&gt;Here's the full application with the nested JSON model:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
import json

from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView
from PyQt6.QtCore import Qt, QAbstractTableModel


class NestedJsonTableModel(QAbstractTableModel):
    """A table model that can display data from nested JSON structures."""

    def __init__(self, data, columns):
        super().__init__()
        self._data = data
        # columns: list of (header_label, dot_separated_key_path)
        self._columns = columns

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._columns)

    def _resolve(self, obj, key_path):
        """Walk into a nested dict using a dot-separated path."""
        keys = key_path.split(".")
        for key in keys:
            if isinstance(obj, dict):
                obj = obj.get(key, "")
            else:
                return ""
        return obj

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            row = self._data[index.row()]
            _, key_path = self._columns[index.column()]
            value = self._resolve(row, key_path)
            if isinstance(value, list):
                return ", ".join(str(v) for v in value)
            return str(value)
        return None

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                label, _ = self._columns[section]
                return label
        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Nested JSON Table Model")

        # Define which fields to show and how to reach them.
        columns = [
            ("Name", "name"),
            ("Age", "age"),
            ("City", "address.city"),
            ("Country", "address.country"),
            ("Skills", "skills"),
        ]

        # Load the nested JSON data.
        with open("people_nested.json", "r") as f:
            data = json.load(f)

        self.model = NestedJsonTableModel(data, columns)
        self.table = QTableView()
        self.table.setModel(self.model)

        # Resize columns to fit their contents.
        self.table.resizeColumnsToContents()

        self.setCentralWidget(self.table)
        self.resize(600, 300)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="adding-sorting-with-qsortfilterproxymodel"&gt;Adding Sorting with QSortFilterProxyModel&lt;/h2&gt;
&lt;p&gt;Once you have your model working, adding sorting is straightforward using &lt;code&gt;QSortFilterProxyModel&lt;/code&gt;. This sits between your model and the view, providing sort and filter capabilities without modifying your original data. For more details on sorting and filtering tables, see our &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-modelview-sort-filter-tables/"&gt;guide to sorting and filtering in Qt Model/View&lt;/a&gt;.&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import sys
import json

from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel


class NestedJsonTableModel(QAbstractTableModel):
    def __init__(self, data, columns):
        super().__init__()
        self._data = data
        self._columns = columns

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._columns)

    def _resolve(self, obj, key_path):
        keys = key_path.split(".")
        for key in keys:
            if isinstance(obj, dict):
                obj = obj.get(key, "")
            else:
                return ""
        return obj

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            row = self._data[index.row()]
            _, key_path = self._columns[index.column()]
            value = self._resolve(row, key_path)
            if isinstance(value, list):
                return ", ".join(str(v) for v in value)
            return str(value)
        return None

    def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole):
        if role == Qt.ItemDataRole.DisplayRole:
            if orientation == Qt.Orientation.Horizontal:
                label, _ = self._columns[section]
                return label
        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Sortable JSON Table")

        columns = [
            ("Name", "name"),
            ("Age", "age"),
            ("City", "address.city"),
            ("Country", "address.country"),
            ("Skills", "skills"),
        ]

        with open("people_nested.json", "r") as f:
            data = json.load(f)

        # Create the source model.
        self.source_model = NestedJsonTableModel(data, columns)

        # Wrap it in a proxy model for sorting.
        self.proxy_model = QSortFilterProxyModel()
        self.proxy_model.setSourceModel(self.source_model)

        self.table = QTableView()
        self.table.setModel(self.proxy_model)
        self.table.setSortingEnabled(True)
        self.table.resizeColumnsToContents()

        self.setCentralWidget(self.table)
        self.resize(600, 300)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Click any column header to sort by that column. Click again to reverse the sort order. The &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; handles all of this automatically &amp;mdash; you don't need to write any sorting logic yourself.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;The Model/View architecture in PyQt6 is designed to work with whatever data you have. The model is a translation layer: it takes your data &amp;mdash; whether it's a flat list, nested JSON, a database query, or anything else &amp;mdash; and presents it in the row-and-column format that Qt's views expect.&lt;/p&gt;
&lt;p&gt;When working with complex data structures, keep these principles in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Your data stays as-is.&lt;/strong&gt; You don't need to restructure your JSON or flatten your database. The model handles the translation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Define your columns explicitly.&lt;/strong&gt; For nested data, decide upfront which fields you want to display and how to reach them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use helper methods.&lt;/strong&gt; A small utility like &lt;code&gt;_resolve()&lt;/code&gt; keeps your &lt;code&gt;data()&lt;/code&gt; method clean and makes it easy to access deeply nested values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layer on functionality.&lt;/strong&gt; Use &lt;code&gt;QSortFilterProxyModel&lt;/code&gt; to add sorting and filtering without touching your model code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're working with tabular data from pandas or numpy instead of JSON, take a look at our tutorial on &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-qtableview-modelviews-numpy-pandas/"&gt;displaying pandas DataFrames in QTableView&lt;/a&gt;. For making your table cells editable, see our guide on &lt;a href="https://www.pythonguis.com/faq/editing-pyqt6-tableview/"&gt;editing data in a PyQt6 QTableView&lt;/a&gt;.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="python"/><category term="model-view"/><category term="qtableview"/><category term="json"/><category term="qt"/><category term="qt6"/></entry></feed>