text: """Python adaptation of https://github.com/dridk/QJsonModel
  
  Supports Python 2 and 3 with PySide, PySide2, PyQt4 or PyQt5.
  Requires https://github.com/mottosso/Qt.py
  
  Usage:
      Use it like you would the C++ version.
  
      >>> import qjsonmodel
      >>> model = qjsonmodel.QJsonModel()
      >>> model.load({"key": "value"})
  
  Test:
      Run the provided example to sanity check your Python,
      dependencies and Qt binding.
  
      $ python qjsonmodel.py
  
  Changes:
      This module differs from the C++ version in the following ways.
  
      1. Setters and getters are replaced by Python properties
      2. Objects are sorted by default, disabled via load(sort=False)
      3. load() takes a Python dictionary as opposed to
         a string or file handle.
  
          - To load from a string, use built-in `json.loads()`
              >>> import json
              >>> document = json.loads("{'key': 'value'}")
              >>> model.load(document)
  
          - To load from a file, use `with open(fname)`
                >>> import json
                >>> with open("file.json") as f:
                ...    document = json.load(f)
                ...    model.load(document)
  
  """
  
  import json
  
  from Qt import QtWidgets, QtCore
  
  
  class QJsonTreeItem(object):
      def __init__(self, parent=None):
          self._parent = parent
  
          self._key = ""
          self._value = ""
          self._type = None
          self._children = list()
  
      def appendChild(self, item):
          self._children.append(item)
  
      def child(self, row):
          return self._children[row]
  
      def parent(self):
          return self._parent
  
      def childCount(self):
          return len(self._children)
  
      def row(self):
          return (
              self._parent._children.index(self)
              if self._parent else 0
          )
  
      @property
      def key(self):
          return self._key
  
      @key.setter
      def key(self, key):
          self._key = key
  
      @property
      def value(self):
          return self._value
  
      @value.setter
      def value(self, value):
          self._value = value
  
      @property
      def type(self):
          return self._type
  
      @type.setter
      def type(self, typ):
          self._type = typ
  
      @classmethod
      def load(self, value, parent=None, sort=True):
          rootItem = QJsonTreeItem(parent)
          rootItem.key = "root"
  
          if isinstance(value, dict):
              items = (
                  sorted(value.items())
                  if sort else value.items()
              )
  
              for key, value in items:
                  child = self.load(value, rootItem)
                  child.key = key
                  child.type = type(value)
                  rootItem.appendChild(child)
  
          elif isinstance(value, list):
              for index, value in enumerate(value):
                  child = self.load(value, rootItem)
                  child.key = str(index)
                  child.type = type(value)
                  rootItem.appendChild(child)
  
          else:
              rootItem.value = value
              rootItem.type = type(value)
  
          return rootItem
  
  
  class QJsonModel(QtCore.QAbstractItemModel):
      def __init__(self, parent=None):
          super(QJsonModel, self).__init__(parent)
  
          self._rootItem = QJsonTreeItem()
          self._headers = ("key", "value")
  
      def load(self, document):
          """Load from dictionary
  
          Arguments:
              document (dict): JSON-compatible dictionary
  
          """
  
          assert isinstance(document, (dict, list, tuple)), (
              "`document` must be of dict, list or tuple, "
              "not %s" % type(document)
          )
  
          self.beginResetModel()
  
          self._rootItem = QJsonTreeItem.load(document)
          self._rootItem.type = type(document)
  
          self.endResetModel()
  
          return True
  
      def json(self, root=None):
          """Serialise model as JSON-compliant dictionary
  
          Arguments:
              root (QJsonTreeItem, optional): Serialise from here
                  defaults to the the top-level item
  
          Returns:
              model as dict
  
          """
  
          root = root or self._rootItem
          return self.genJson(root)
  
      def data(self, index, role):
          if not index.isValid():
              return None
  
          item = index.internalPointer()
  
          if role == QtCore.Qt.DisplayRole:
              if index.column() == 0:
                  return item.key
  
              if index.column() == 1:
                  return item.value
  
          elif role == QtCore.Qt.EditRole:
              if index.column() == 1:
                  return item.value
  
      def setData(self, index, value, role):
          if role == QtCore.Qt.EditRole:
              if index.column() == 1:
                  item = index.internalPointer()
                  item.value = str(value)
  
                  # if __binding__ in ("PySide", "PyQt4"):
                  self.dataChanged.emit(index, index)
                  # else:
                  #     self.dataChanged.emit(index, index, [QtCore.Qt.EditRole])
  
                  return True
  
          return False
  
      def headerData(self, section, orientation, role):
          if role != QtCore.Qt.DisplayRole:
              return None
  
          if orientation == QtCore.Qt.Horizontal:
              return self._headers[section]
  
      def index(self, row, column, parent=QtCore.QModelIndex()):
          if not self.hasIndex(row, column, parent):
              return QtCore.QModelIndex()
  
          if not parent.isValid():
              parentItem = self._rootItem
          else:
              parentItem = parent.internalPointer()
  
          childItem = parentItem.child(row)
          if childItem:
              return self.createIndex(row, column, childItem)
          else:
              return QtCore.QModelIndex()
  
      def parent(self, index):
          if not index.isValid():
              return QtCore.QModelIndex()
  
          childItem = index.internalPointer()
          parentItem = childItem.parent()
  
          if parentItem == self._rootItem:
              return QtCore.QModelIndex()
  
          return self.createIndex(parentItem.row(), 0, parentItem)
  
      def rowCount(self, parent=QtCore.QModelIndex()):
          if parent.column() > 0:
              return 0
  
          if not parent.isValid():
              parentItem = self._rootItem
          else:
              parentItem = parent.internalPointer()
  
          return parentItem.childCount()
  
      def columnCount(self, parent=QtCore.QModelIndex()):
          return 2
  
      def flags(self, index):
          flags = super(QJsonModel, self).flags(index)
  
          if index.column() == 1:
              return QtCore.Qt.ItemIsEditable | flags
          else:
              return flags
  
      def genJson(self, item):
          nchild = item.childCount()
  
          if item.type is dict:
              document = {}
              for i in range(nchild):
                  ch = item.child(i)
                  document[ch.key] = self.genJson(ch)
              return document
  
          elif item.type == list:
              document = []
              for i in range(nchild):
                  ch = item.child(i)
                  document.append(self.genJson(ch))
              return document
  
          else:
              return item.value
  
  
  if __name__ == '__main__':
      import sys
  
      app = QtWidgets.QApplication(sys.argv)
      view = QtWidgets.QTreeView()
      model = QJsonModel()
  
      view.setModel(model)
  
      document = json.loads("""\
      {
          "firstName": "John",
          "lastName": "Smith",
          "age": 25,
          "address": {
              "streetAddress": "21 2nd Street",
              "city": "New York",
              "state": "NY",
              "postalCode": "10021"
          },
          "phoneNumber": [
              {
                  "type": "home",
                  "number": "212 555-1234"
              },
              {
                  "type": "fax",
                  "number": "646 555-4567"
              }
          ]
      }
      """)
  
      model.load(document)
      model.clear()
      model.load(document)
  
      # Sanity check
      assert (
          json.dumps(model.json(), sort_keys=True) ==
          json.dumps(document, sort_keys=True)
      )
  
      view.show()
      view.resize(500, 300)
      app.exec_()
  