# \<ObjectWrapper\> issue while writing the data

**URL:** <https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124>\
**Category:** Ignition\
**Tags:** scripting, database, perspective\
**Created:** [August 24, 2024, 11:03pm UTC](https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124 "2024-08-24T23:03:16Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Farhan\_Basheer](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/farhan_basheer/32/66759_2.png) [@Farhan\_Basheer](https://forum.inductiveautomation.com/u/Farhan_Basheer)\
**Post date:** [August 24, 2024, 11:03pm UTC](https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124/1 "2024-08-24T23:03:16Z")

</div>

#### **Description:**

I am encountering an issue with the format of the data rows in an Ignition script. Specifically, the script is receiving rows in a format that is not a standard dictionary but an `ObjectWrapper`. This is causing errors when trying to process and save the data.  
**Error Message:**  
`Unexpected row format (not a dictionary): <type 'com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ObjectWrapper'>: <ObjectWrapper>: {u'BrassTag_Desc': u'far', u'BrassTag': u'8900'}`

#### **Script Details:**

The script aims to process data from a Table component and save each row to a database. Here's a snippet of the relevant code:

```python
def runAction(self, event):
    # Get the logger instance
    logger = system.util.getLogger("BrassTag_Save")

    try:
        # Get the data from the Table component
        tbl = self.getSibling("Table").props.data

        # Ensure tbl is not None
        if tbl is None:
            logger.error("Table data is None. Aborting save operation.")
            return

        # Convert ObjectWrapper to list of dictionaries if needed
        if hasattr(tbl, 'value'):
            tbl = tbl.value

        # Handle the case where there might not be enough rows
        if len(tbl) < 2:
            lastBrassTag = None
            logger.info("Not enough rows to determine lastBrassTag.")
        else:
            lastBrassTag = tbl[-2].get("BrassTag", None)
            logger.info("Last BrassTag: {}".format(lastBrassTag))

        # Run the Named Query to save each row in tbl
        for row in tbl:
            # Convert ObjectWrapper to dictionary if needed
            if hasattr(row, 'value'):
                row = row.value

            # Check if the row is a dictionary
            if isinstance(row, dict):
                brass_tag = row.get("BrassTag")
                brass_tag_desc = row.get("BrassTag_Desc")
                
                if brass_tag:
                    system.db.runNamedQuery("Query_BrassTag_Insert",
                                            {"BrassTag": brass_tag,
                                             "BrassTag_Desc": brass_tag_desc,
                                             "user": self.session.props.auth.user.userName})
                    logger.info("Saved BrassTag: {}, BrassTag_Desc: {}".format(brass_tag, brass_tag_desc))
                else:
                    logger.error("Row missing 'BrassTag' key or 'BrassTag' is empty: {}".format(row))
            else:
                # Log the type and content of row for debugging
                logger.error("Unexpected row format (not a dictionary): {}: {}".format(type(row), row))

        # Clear the Table data after saving
        self.getSibling("Table").props.data = []
        logger.info("Cleared Table data after saving.")

    except Exception as e:
        logger.error("Error during BrassTag save operation: {}".format(e))

```

#### **Issue Details:**

- Writing some data from a "Table" to database
- **Expected Format:** Dictionary
- **Actual Format:** `ObjectWrapper` with dictionary-like content
- **Example Row:** `{u'BrassTag_Desc': u'far', u'BrassTag': u'8900'}`

#### **Steps Taken:**

- Checked if `tbl` and `row` are `ObjectWrapper` instances and attempted conversion using `row.value`.
- Verified that `tbl` and `row` should be dictionaries.

#### **Request for Help:**

- How can I handle `ObjectWrapper` instances correctly to ensure they are processed as dictionaries?
- Is there a different method to convert or extract data from `ObjectWrapper` in Ignition?

Thank you for your assistance.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [August 25, 2024, 12:22am UTC](https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124/2 "2024-08-25T00:22:40Z")

</div>

> [@Farhan\_Basheer](#):
>
> - **Expected Format:** Dictionary
> - **Actual Format:** `ObjectWrapper` with dictionary-like content

What was the actual problem you were encountering before you attempted to coerce the types?

The bandaid solution is basically just to do what you're doing, but recursively, to flatten Perspective's faux objects into native Python objects:

> [@Function to convert json string or python dict into ObjectWrapper](https://forum.inductiveautomation.com/t/function-to-convert-json-string-or-python-dict-into-objectwrapper/32861/16):
>
> Forcing each to the expected type while traversing makes a deep copy: def sanitize\_tree(element): if hasattr(element, '\_\_iter\_\_'): if hasattr(element, 'keys'): return dict((k, sanitize\_tree(element[k])) for k in element.keys()) else: return list(sanitize\_tree(x) for x in element) return element EDIT: Got rid of the unnecessary zip() and used only one keys() iterator instead of two.

But ideally, you wouldn't have to do this at all; the objects you get _are_ dictionaries and lists, according to duck typing, so you should ideally be able to pass them through other mechanisms without correction.

---

<div class="post-metadata">

**Author:** ![Farhan\_Basheer](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/farhan_basheer/32/66759_2.png) [@Farhan\_Basheer](https://forum.inductiveautomation.com/u/Farhan_Basheer)\
**Post date:** [August 25, 2024, 6:05am UTC](https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124/3 "2024-08-25T06:05:38Z")

</div>

Basically I simply just want to save some data in the database

```python
def runAction(self, event):
	tbl = self.getSibling("Table").props.data
	lastBrassTag = tbl[len(tbl) - 2]["BrassTag"]

	for row in tbl:
		if row["BrassTag"] != "":
			system.db.runNamedQuery("Query_BrassTag_Insert", 
			{"BrassTag":row['BrassTag'], "BrassTag_Desc":row['BrassTag_Desc'], "user":self.session.props.auth.user.userName})

```

Initially I get this error

```python
Table data retrieved: <ArrayWrapper>: [{u'BrassTag_Desc': u'fr', u'BrassTag': u'5200'}]

```

Fixing this takes me to another then another.

I don't understand why it can't be just simple reading form Table and Writing it into the database...

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [August 25, 2024, 9:19am UTC](https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124/4 "2024-08-25T09:19:33Z")

</div>

I assume this is related to your other post concerning popups ?

Let's put everything together and take it from the top. So, quick recap, to make things clear.

- You have a view with a table.
- People can add rows to that table
- There's a "save" button, which opens a confirmation popup
- When confirmed, the table is saved to the database

Now some questions:

- Which rows should be inserted ? It appears some rows don't have a value for the column `BrassTag`, in which case you don't want to insert them. Anything else ?
- Are ALL rows new ? Or are you loading data from somewhere, and in this case what happens to the data that was already there ? Can it be modified ?
- What's the point of `lastBrassTag` ? You're not using it for anything except logging. And why is it at the index "-2" ? Is the last row a special one ?

For the simplest case, I'll assume that all data is new, and that you're following the method I suggested in the other thread.  
It will look something like this:

- There's a custom property on the view called `data`
- The table's `prop.data` property is bound to that custom prop
- You have a mechanism in place to add rows to `view.custom.data`
- This mechanism makes sure the data is properly formatted for a table: All rows have keys for all columns
- The "save" buttons opens a popup as discussed in the other thread. Let's call the message handler "insert\_brass\_tags" for future references
- Put a message handler called "insert\_brass\_tags" on the view's root - And I mean the base view, not the popup
- The script in this message handler looks like this:

```python
if payload['confirm'] and self.view.custom.data:
	data = [
		{
			'brass_tag': row['brassTag'],
			'brass_tag_Desc': row['BrassTag_Desc'],
			'user': self.session.props.auth.user.username
		} for row in self.view.custom.data if row['BrassTag']
	]
	q, v = build_insert_string('brass_tag_table', data)
	system.db.runPrepUpdate(q, v)
	self.view.custom.data = []

```

That's it.  
I'm leaving out error handling for now, let's get the logic right first.  
Also, note that the keys in the `data` dictionnary should match the column names of your database table.

* * *

Side notes:  
Drop the pointless comments.  
ie:

```python
 # Get the logger instance
logger = system.util.getLogger("BrassTag_Save")

```

The function is called `getLogger`. You don't need to comment that you're getting a logger.

```python
# Get the data from the Table component
tbl = self.getSibling("Table").props.data

```

It's a simple assignation. This doesn't need to be explained

```python
# Ensure tbl is not None
if tbl is None:

```

The comment basically translates code into a full english sentence... Which really isn't needed.

Use comments to explain what you do, not describe it.  
For exemple:

```python
# Convert ObjectWrapper to list of dictionaries if needed
if hasattr(tbl, 'value'):
    tbl = tbl.value

```

How does that convert anything to a list of dictionaries ? The comment doesn't help me understand what's going on, it even raises more questions than it answers.

---

<div class="post-metadata">

**Author:** ![Farhan\_Basheer](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/farhan_basheer/32/66759_2.png) [@Farhan\_Basheer](https://forum.inductiveautomation.com/u/Farhan_Basheer)\
**Post date:** [August 25, 2024, 11:04am UTC](https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124/5 "2024-08-25T11:04:14Z")

</div>

Exactly, It is related to the same issue.  
There's one point missing from the recap.  
When We click on the Confirm Button and a Confirmation Popup `ConfirmSave` opens, there's another `Table` on this popup. I want to get the Data from this `Table` and write it into the Database.

This Table is Binded with the Table `Tbl_Insert_New_BrassTag` on the View using Data Property.

---

<div class="post-metadata">

**Author:** ![Farhan\_Basheer](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/farhan_basheer/32/66759_2.png) [@Farhan\_Basheer](https://forum.inductiveautomation.com/u/Farhan_Basheer)\
**Post date:** [August 25, 2024, 11:21am UTC](https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124/6 "2024-08-25T11:21:38Z")

</div>

> [@pascal.fragnoud](#):
>
> Which rows should be inserted ? It appears some rows don't have a value for the column `BrassTag`, in which case you don't want to insert them. Anything else ?

The Data is being transferred from View Table to the Popup Table in this format `{'BrassTag_Desc':Desc 1234, 'BrassTag': 1234}`

So on the Popup Table the First Column is "BrassTag\_Desc" and Second is "BrassTag" but I am switching the Columns using Column Property on the Popup Table.

[In this Video, I have Described the Issue and Wrokflow in Detail.](https://drive.google.com/file/d/1VZbjXfwuRsMIcHYVoJQlbhB5yKuz4Ecx/view?usp=sharing)

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [August 25, 2024, 11:35am UTC](https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124/7 "2024-08-25T11:35:30Z")

</div>

> [@Farhan\_Basheer](#):
>
> I want to get the Data from this "Table" and write it into the Database.

Do NOT do this. That's what all my previous messages were about.  
You can pass data to your confirmation popup for display purposes, but DO NOT write to the db from there.

Follow the steps in my post above, you'll see the data is written to the database from the initial view. This makes the popup completely irrelevant to the database operations. It's there for display and confirmation only. You can display anything you want there without affecting the operations in the calling view.

If there's no special processing of the data, those steps all you need. You may want to go back to the other thread and the confirmation popup I described to understand how it works.

---

<div class="post-metadata">

**Author:** ![Farhan\_Basheer](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/farhan_basheer/32/66759_2.png) [@Farhan\_Basheer](https://forum.inductiveautomation.com/u/Farhan_Basheer)\
**Post date:** [August 25, 2024, 11:39am UTC](https://forum.inductiveautomation.com/t/objectwrapper-issue-while-writing-the-data/95124/8 "2024-08-25T11:39:03Z")

</div>

Ok, Let me Try What you've suggested. I am very new to Ignition, that's why I thought using a different approach maybe took more time.
