# System.tag.browse returns different number of keys

**URL:** <https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225>\
**Category:** Ignition\
**Created:** [October 7, 2025, 1:04am UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225 "2025-10-07T01:04:38Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [October 7, 2025, 1:04am UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/1 "2025-10-07T01:04:38Z")

</div>

WHAT?!. Is the below even normal?  
`system.tag.browse` returns different number of keys:

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/e/5/e553ef6b18abcddf5aa7d57a718b3a7abb1a2504.png)

My code:

```python
	path = value['path']
	fltr={}
	fltr['recursive'] = value['isRecursive']
	results = system.tag.browse(path, fltr)
	
	return results

```

I would really expect it, to return same number of keys, to present it using table..

If this is the case, table component returns error.  
How can I fix this?

---

<div class="post-metadata">

**Author:** ![dgillbee](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/dgillbee/32/73838_2.png) [@dgillbee](https://forum.inductiveautomation.com/u/dgillbee)\
**Post date:** [October 7, 2025, 1:59am UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/2 "2025-10-07T01:59:16Z")

</div>

The top tag is a UDT instance. That's why they aren't the same it's not an actual atomic tag.  
It looks like this code snippet is from a binding where you are passing in a dictionary with the property `isRecursive` into it.

You could simplify the code a little:

```python
fltr={
    'recursive' : value['isRecursive']
}

```

If you don't want the UDT defintion in the returned list, but do want the child tags then add `tagType` to the filter.

```python
	fltr={
		'recursive':True,
		'tagType':'AtomicTag'
		}

```

---

<div class="post-metadata">

**Author:** ![dgillbee](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/dgillbee/32/73838_2.png) [@dgillbee](https://forum.inductiveautomation.com/u/dgillbee)\
**Post date:** [October 7, 2025, 2:05am UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/3 "2025-10-07T02:05:24Z")

</div>

If you still have issues with the keys returned then you could filter the return keys with a requried list.

```python
def transform(self, value, quality, timestamp):
	fltr={
		'recursive':True,
		'tagType':'AtomicTag'
		}
	path = '[default]myTags'
	fullTags = system.tag.browse(path,fltr)
	requiredKeys = ['fullPath','value','name','fakeProp']
	return [{k:tag.setdefault(k,None) for k in requiredKeys} for tag in fullTags]

```

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [October 7, 2025, 2:38am UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/4 "2025-10-07T02:38:33Z")

</div>

My intention is to dispay tag properties of all child tags of different tag types from base parent path.

i guess you are right tag.browse usually use to return one type of tag. I could get all the unique key of list of dict and use python get method to return vale when key does not exist.

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [October 7, 2025, 3:19am UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/5 "2025-10-07T03:19:56Z")

</div>

```python
	path = value['path']
	fltr={}
	fltr['recursive'] = value['isRecursive']
	lod = system.tag.browse(path, fltr)
	
	keys = list(set(key for d in lod for key in d.keys()))
	
	newLod = []
	for d in lod:
		d0={}
		for key in keys:
			if key=="value":
				d0[key] = d[key].getValue()
			else:
				d0[key] = str(d.get(key, ""))
		newLod.append(d0)
	
	return newLod

```

---

<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:** [October 7, 2025, 7:55am UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/6 "2025-10-07T07:55:44Z")

</div>

I have a few suggestions:

- You don't have to convert the set to a list. In fact, sets are more efficient for membership checks
- I'd keep the `None` and the actual datatype instead of converting to strings. You can manage how nulls appear in a table with the `nullFormat` prop:  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/3/d/3d6d4e6c7e4edc246464c86f41c1c7f50e9c2aa3.png)
- maybe exclude folders ? `lod = filter(lambda tag: tag['tagType'] is not tag['tagType'].UdtInstance, lod)`

All in all, I'd probably write it like this:

```python
	lod = system.tag.browse(
		value['path'],
		filter={'recursive': value['isRecursive']}
	)
	lod = filter(lambda tag: tag['tagType'] is not tag['tagType'].Folder, lod)

	keys = set(key for d in lod for key in d.keys())
	data = [{k: d.get(k) for k in keys} for d in lod]
	for d in data:
		d['value'] = d['value'].value
	return data

```

last thing: Pick a better name than `lod`. What does it mean ? The only idea I have is "List Of Dicts".

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [October 7, 2025, 2:00pm UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/7 "2025-10-07T14:00:31Z")

</div>

I built this tool for education purpose, where I have tag browse tree on the left.  
To understand more on about tags.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/4/64bf177a0a715ad3b8c260bc558dbf11ea00cfc7.png)

> [@pascal.fragnoud](#):
>
> You don't have to convert the set to a list. In fact, sets are more efficient for membership checks

You are right no need to convert to list. During debuging, I return the 'set', but custom property returned a string. So I wrapped it with list. (forgot to remove them).

> [@pascal.fragnoud](#):
>
> I'd keep the `None` and the actual datatype instead of converting to strings. You can manage how nulls appear in a table with the `nullFormat` prop

This is very true. I was avoiding more debug, so decide to set to harmless string.

> [@pascal.fragnoud](#):
>
> `lod`. What does it mean

Yes i came up with lod means "list of dictionary". ds for dataset.  
For list i set to plural.  
Very helpful, when my mind read lod, immediately i know it is literally a list of dict.

I appreciate you looking at my code.

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [October 7, 2025, 2:32pm UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/8 "2025-10-07T14:32:47Z")

</div>

> [@pascal.fragnoud](#):
>
> `data = [{k: d.get(k) for k in keys} for d in lod]`

This is nice, list of dict comprehension.

```python
newLod = [{k: d.get(k) for k in keys} for d in lod]

```

But I see the expanded version is more flexible/reusable code in this case.  
Why would you loop thru each key-value pair, if you won't do some processing.

---

<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:** [October 7, 2025, 2:44pm UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/9 "2025-10-07T14:44:58Z")

</div>

> [@Eugene49](#):
>
> This is very true. I was avoiding more debug, so decide to set to harmless string.

You may end up having more debug to do by converting to strings, as it may hide errors that would otherwise be obvious.

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [October 7, 2025, 6:09pm UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/10 "2025-10-07T18:09:26Z")

</div>

> [@Eugene49](#):
>
> Why would you loop thru each key-value pair, if you won't do some processing.

To prepare for the processing.

In this case you can actually eliminate the loop after as well.

```python
newLod = [{k: d.get(k).value if k == 'value' else d.get(k) for k in keys} for d in lod]

```

So the script as a whole could be:

```python
lod =system.tag.browse(
    value['path'],
    filter={'recursive': value['isRecursive']}
)
lod = filter(lambda tag: tag['tagType'] in not tag['tagType'].Folder, log)

keys = set(key for d in log for key in d.keys())
return [{k: d.get(K).value if k == 'value' else d.get(K) for k in keys} for d in lod]

```

---

<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:** [October 8, 2025, 2:51pm UTC](https://forum.inductiveautomation.com/t/system-tag-browse-returns-different-number-of-keys/111225/11 "2025-10-08T14:51:05Z")

</div>

I missed this question when I last answered...  
I find it more readable.  
We build a list of dicts, because that's what we need.  
Then we reprocess what needs to be reprocessed.

Yes, there are 2 loops when it could be done in one.  
But I find it makes things clearer, and unless you're processing a lot of rows, I doubt you'll see any difference.  
I usually write for clarity of intent first, then rewrite it if needed.  
Note that I specifically say "_of intent_", which is not always the same a general readability.  
It's a distinction I recently started to make, because some people will find iterative loops more generally readable than comprehensions, but I don't think anyone can argue that comprehensions WITH EXPLICIT NAMES don't convey the intent better.  
This is also why I prefer using `filter()` than filtering in a comprehension: clarity of intent.  
And it's also why I make helper functions with just one line.
