# Json compare tool for Views and Tags?

**URL:** <https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163>\
**Category:** Ignition\
**Tags:** ignition80, scripting\
**Created:** [December 3, 2020, 4:56am UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163 "2020-12-03T04:56:30Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [December 3, 2020, 4:56am UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/1 "2020-12-03T04:56:30Z")

</div>

Just wondering if anyone’s written a JSON compare tool for comparing Views and Tags?  
The generic JSON compare tools online are a bit useless for Ignition.

I want be able to see what’s changed between different versions of the project, and it would be particularly useful to show all the differences between an offline dev and live system as a way to catch any missed resources when deploying new changes, especially larger projects developed offline.

Idea added:  
[https://ideas.inductiveautomation.com/ignition-features-and-ideas/p/add-json-compare-tool-for-views-and-tags-to-show-differences](https://ideas.inductiveautomation.com/ignition-features-and-ideas/p/add-json-compare-tool-for-views-and-tags-to-show-differences)

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [December 3, 2020, 1:58pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/2 "2020-12-03T13:58:34Z")

</div>

Is the way the json structure\order for views consistent? Almost seems like you’d just want to use github to track changes.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [December 3, 2020, 5:25pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/3 "2020-12-03T17:25:13Z")

</div>

Yes it’s consistent. Github will be able to tell you all of the changes made, but not in a friendly way. It won’t tell you “these tags were added, these tags were deleted, these tags had these properties change value, etc.”  
It’ll tell you “this part of the json is different, this part was removed” but it won’t be able to associate it to a tag which is the uid for us

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [December 6, 2020, 10:05pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/4 "2020-12-06T22:05:18Z")

</div>

I wrote something over the weekend in Python to compare tags. It works pretty good! Although I’m finding that there are a lot of properties deleted between tags that were upgraded by a gateway upgrade and the same tags exported from v7 as XML and imported into the v8 system. Lots of properties like “tagGroup”, “readOnly”, “read/writePermissions” and their sub-objects.  
I also found that migration from v7 to v8 doesn’t migrate UDT parameters across with their correct dataType. **All** of my UDT definitions use String-type parameters, however all of these parameters were converted to Integer. The v7 XML export/import to v8 had the correct String dataType.

I ran the tool over a UDT that had some minor changes made to it by another engineer (we change-freezed to upgrade, but urgent on-site work meant that changes had to be made, so they noted which resources they’re made changes to so I could migrate them back into the upgraded project), and it picked up 1400 or so changes. However, when I ran through them, I found there were only 2 modifications that were actually useful, with the rest being all of the other things mentioned above. I assume it will report more useful things however once only working in v8.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [November 13, 2023, 8:26pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/5 "2023-11-13T20:26:25Z")

</div>

I recently rewrote my tag compare script which was a bit dopey, to use a Python library `littletree` I found via StackOverflow. Most of the kudos goes to its developer who answered my [SO question](https://stackoverflow.com/questions/77443116/how-to-compare-two-json-files-that-adhere-to-a-schema/77463485?noredirect=1#comment136580884_77463485) with the majority of the code, but I modified it to include more detail and spit out a CSV.

This will only run in CPython, I use PyCharm as my IDE. It requires a min of Python 3.10 (dependency of the `littletree` library).  
The only library dependency not included in the standard libraries is `littletree` (`pip install littletree`)

```python
from littletree import Node # littletree requires minimum Python 3.10
import json

def compare_tag_json(json_orig_filepath, json_new_filepath, exclude_tags_in_removed_folders=True, exclude_tags_in_added_udt_instances=True):
	"""
	This is a wrapper for function compare_tag_obj.
	Takes two tag JSON exports to files and produces a report of changes between the tag structures, including:
		- additions/deletions of tags
		- additions/modifications/removal of tag props
	
	Args:
		json_orig_filepath:
			The filepath to the original tag json file for the comparison.
		json_new_filepath:
			The filepath to the new tag json file for the comparison.
		exclude_tags_in_removed_folders:
			For folders removed in the new file, this option will exclude the tag changes (i.e. deletions) to the tags
			contained within the folder. 
			Default: True
		exclude_tags_in_added_udt_instances:
			For UDT Instances added in the new file, this option will exclude the tag changes (i.e. additions) to the 
			tags contained within the UDT Instance.
			Default: True
	
	Returns:
		The table of changes in CSV format with headers: tagpath, tagtype, change, from, to 
		Also prints the results as a CSV to the console.
	"""
	with open(json_orig_filepath, 'r') as f:
		json_orig = f.read()

	with open(json_new_filepath, 'r') as f:
		json_new = f.read()

	original = json.loads(json_orig)
	modified = json.loads(json_new)

	return compare_tag_obj(original, modified, exclude_tags_in_removed_folders, exclude_tags_in_added_udt_instances)

def compare_tag_obj(tags_original, tags_modified, exclude_tags_in_removed_folders=True, exclude_tags_in_added_udt_instances=True):
	"""
	Takes two tag JSON as Py lists/dicts and produces a report of changes between the tag structures, including:
		- additions/deletions of tags
		- additions/modifications/removal of tag props
	
	Args:
		tags_original:
			The list/dict structure of the original tag json file for the comparison.
		tags_modified:
			The list/dict structure of the new tag json file for the comparison.
		exclude_tags_in_removed_folders:
			For folders removed in the new file, this option will exclude the tag changes (i.e. deletions) to the tags
			contained within the folder. 
			Default: True
		exclude_tags_in_added_udt_instances:
			For UDT Instances added in the new file, this option will exclude the tag changes (i.e. additions) to the 
			tags contained within the UDT Instance.
			Default: True
	
	Returns:
		The table of changes in CSV format with headers: tagpath, tagtype, change, from, to 
		Also prints the results as a CSV to the console.
	"""
	original_tree = Node.from_dict(tags_original, identifier_name='name', children_name='tags')
	modified_tree = Node.from_dict(tags_modified, identifier_name='name', children_name='tags')

	# Collect changes in a list
	changes = []
	for diff_node in original_tree.compare(modified_tree).iter_tree():
		diff_data = diff_node.data
		tagpath = str(diff_node.path)[1:]
		if not diff_data:
			continue # Data was the same

		if 'self' not in diff_data:
			if not exclude_tags_in_added_udt_instances or not any(change['tagpath'] in tagpath and change['change'] == 'Tag added' and change['tagtype'] == 'UdtInstance' for change in changes):
				changes.append({'tagpath': tagpath,
								'tagtype': diff_data['other']['tagType'],
								'change': 'Tag added',
								'from': None,
								'to': None})
		elif 'other' not in diff_data:
			if not exclude_tags_in_removed_folders or not any(change['tagpath'] in tagpath and change['change'] == 'Tag removed' for change in changes):
				changes.append({'tagpath': tagpath,
								'tagtype': diff_data['self']['tagType'],
								'change': 'Tag removed',
								'from': None,
								'to': None})
		else:
			original_data, modified_data = diff_data['self'], diff_data['other']
			all_keys = set(list(original_data.keys()) + list(modified_data.keys()))

			for key in all_keys:
				if key in original_data and key in modified_data:
					if original_data[key] != modified_data[key]:
						changes.append({'tagpath': f'{tagpath}.{key}',
										'tagtype': original_data['tagType'],
										'change': 'Modified property',
										'from': original_data[key],
										'to': modified_data[key]
										})
				elif key in original_data:
					changes.append({'tagpath': f'{tagpath}.{key}',
									'tagtype': original_data['tagType'],
									'change': 'Removed property',
									'from': original_data[key],
									'to': None
									})
				elif key in modified_data:
					changes.append({'tagpath': f'{tagpath}.{key}',
									'tagtype': modified_data['tagType'],
									'change': 'Added property',
									'from': None,
									'to': modified_data[key]})

	changes_csv = '"' + '","'.join(changes[0].keys()) + '"\r\n'
	changes_csv += '\r\n'.join('"' + '","'.join(map(str, change.values())) + '"' for change in changes)
	print(changes_csv)
	return changes_csv

```

The result is a table like this:

| tagpath | tagtype | change | from | to |
| --- | --- | --- | --- | --- |
| A/New Tag 1.value | AtomicTag | Added property | None | 1 |
| A/New Instance | UdtInstance | Tag removed | None | None |
| A/New Tag 2 | AtomicTag | Tag removed | None | None |
| A/New Tag.value | AtomicTag | Modified property | 2 | 1 |
| A/Folder B | Folder | Tag added | None | None |
| A/New Instance 3 | UdtInstance | Tag added | None | None |

or this:

| tagpath | tagtype | change | from | to |
| --- | --- | --- | --- | --- |
| Analogue Input 01.parameters | UdtType | Modified property | {'Alarm\_ParentDevice': {'dataType': 'String'}, 'Unit': {'dataType': 'String', 'value': ''}, 'Alarm\_Area': {'dataType': 'String'}, 'Format': {'dataType': 'String', 'value': '#,##0.0'}, 'PLCName': {'dataType': 'String', 'value': {'bindType': 'parameter', 'binding': '{PLCName}'}}, 'Description': {'dataType': 'String', 'value': ''}, 'DeviceName': {'dataType': 'String', 'value': ''}, 'Global.': {'dataType': 'String', 'value': ''}} | {'Alarm\_ParentDevice': {'dataType': 'String'}, 'Unit': {'dataType': 'String'}, 'Alarm\_Area': {'dataType': 'String'}, 'Format': {'dataType': 'String', 'value': '#,##0.0'}, 'PLCName': {'dataType': 'String', 'value': {'bindType': 'parameter', 'binding': '{PLCName}'}}, 'Description': {'dataType': 'String', 'value': ''}, 'DeviceName': {'dataType': 'String', 'value': ''}, 'Global.': {'dataType': 'String', 'value': ''}} |
| Analogue Input 01/Low Alarm SP | AtomicTag | Tag removed | None | None |
| Analogue Input 01/Alarms/High | AtomicTag | Tag removed | None | None |
| Analogue Input 01/Alarms/\_AnyActive.executionMode | AtomicTag | Removed property | EventDriven | None |
| Analogue Input 01/Alarms/\_AnyActive.expression | AtomicTag | Removed property | {[.]High High} | |
| Analogue Input 01/Alarms/\_AnyActive.opcItemPath | AtomicTag | Modified property | {'bindType': 'parameter', 'binding': 'ns=1;s=[{PLCName}]{Global.}{DeviceName}.Alarms'} | {'bindType': 'parameter', 'binding': 'ns=1;s=[{PLCName}]{Global.}{DeviceName}.AnyAlarm'} |
| Analogue Input 01/Alarms/Low Low | AtomicTag | Tag removed | None | None |
| Analogue Input 01/Alarms/Low | AtomicTag | Tag removed | None | None |
| Analogue Input 01/Alarms/Comms Fault | AtomicTag | Tag removed | None | None |
| Analogue Input 01/Alarms/Signal Fault | AtomicTag | Tag added | None | None |
| Analogue Input 01/Alarms/Over or Under Range | AtomicTag | Tag added | None | None |
| Analogue Input 01/Simulation | AtomicTag | Tag removed | None | None |
| Analogue Input 01/High Alarm SP | AtomicTag | Tag removed | None | None |
| Analogue Input 01/Low Low Alarm SP | AtomicTag | Tag removed | None | None |
| Analogue Input 01/Low Alarm Time | AtomicTag | Tag removed | None | None |
| Analogue Input 01/High High Alarm Time | AtomicTag | Tag removed | None | None |
| Analogue Input 01/High Alarm Time | AtomicTag | Tag removed | None | None |
| Analogue Input 01/Low Low Alarm Time | AtomicTag | Tag removed | None | None |
| Analogue Input 01/High High Alarm SP | AtomicTag | Tag removed | None | None |

> **Test Code**
>
> ```python
> original = {
> 'name': 'A',
> 'tagType': 'Folder',
> 'tags': [
> {
> 'valueSource': 'memory',
> 'name': 'New Tag 1',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'New Instance',
> 'typeId': 'Alarm Summary',
> 'parameters': {
> 'MainTagPath': {
> 'dataType': 'String',
> 'value': 'Bob'
> },
> 'Description': {
> 'dataType': 'String',
> 'value': 'Descrp'
> }
> },
> 'tagType': 'UdtInstance',
> 'tags': [
> {
> 'name': 'Area PLCs for Alarm Reset',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'Active Ack',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'Active Unack',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'Clear Unack',
> 'tagType': 'AtomicTag'
> }
> ]
> },
> {
> 'valueSource': 'memory',
> 'name': 'New Tag 2',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'New Instance 1',
> 'typeId': 'Alarm Summary',
> 'parameters': {
> 'MainTagPath': {
> 'dataType': 'String',
> 'value': 'Bob'
> },
> 'Description': {
> 'dataType': 'String',
> 'value': 'Descrp'
> }
> },
> 'tagType': 'UdtInstance',
> 'tags': [
> {
> 'name': 'Active Ack',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'Area PLCs for Alarm Reset',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'Clear Unack',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'Active Unack',
> 'tagType': 'AtomicTag'
> }
> ]
> },
> {
> 'valueSource': 'memory',
> 'name': 'New Tag',
> 'value': 2,
> 'tagType': 'AtomicTag'
> }
> ]
> }
> 
> modified = {
> 'name': 'A',
> 'tagType': 'Folder',
> 'tags': [
> {'name': 'Folder B',
> 'tagType': 'Folder'
> },
> {
> 'valueSource': 'memory',
> 'name': 'New Tag',
> 'value': 1,
> 'tagType': 'AtomicTag'
> },
> {
> 'valueSource': 'memory',
> 'name': 'New Tag 1',
> 'value': 1,
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'New Instance 3',
> 'typeId': 'Alarm Summary',
> 'parameters': {
> 'MainTagPath': {
> 'dataType': 'String',
> 'value': 'Bob'
> },
> 'Description': {
> 'dataType': 'String',
> 'value': 'Descrp'
> }
> },
> 'tagType': 'UdtInstance',
> 'tags': [
> {
> 'name': 'Area PLCs for Alarm Reset',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'Clear Unack',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'Active Unack',
> 'tagType': 'AtomicTag'
> },
> {
> 'name': 'Active Ack',
> 'tagType': 'AtomicTag'
> }
> ]
> }
> ]
> }
> 
> compare_tag_obj(original, modified, exclude_tags_in_removed_folders=True, exclude_tags_in_added_udt_instances=True)
> 
> ```

---

<div class="post-metadata">

**Author:** ![lachguerabdallah](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lachguerabdallah/32/25558_2.png) [@lachguerabdallah](https://forum.inductiveautomation.com/u/lachguerabdallah)\
**Post date:** [May 15, 2024, 12:50pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/6 "2024-05-15T12:50:58Z")

</div>

Hi,  
How can I run your code?  
do you have any example?  
thank you

---

<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:** [May 15, 2024, 1:11pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/7 "2024-05-15T13:11:35Z")

</div>

Nick was/is using PyCharm to execute this code, but I would expect any Python 3.1+ environment would be able to run it, so pick your poison.

Note: It will not run inside of an Ignition Designer or Client without some fancy footwork to execute it externally.

---

<div class="post-metadata">

**Author:** ![lachguerabdallah](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lachguerabdallah/32/25558_2.png) [@lachguerabdallah](https://forum.inductiveautomation.com/u/lachguerabdallah)\
**Post date:** [May 15, 2024, 1:27pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/8 "2024-05-15T13:27:30Z")

</div>

yes, I am running it within VS code, I installed the litletree library

and I am calling the function passing the path of my Json.  
compare\_tag\_json("C:\Users\l.abdallah\Desktop\original.json", "C:\Users\l.abdallah\Desktop\modified.json", exclude\_tags\_in\_removed\_folders=True, exclude\_tags\_in\_added\_udt\_instances=True)

and I don't have any results.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [May 15, 2024, 10:46pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/9 "2024-05-15T22:46:17Z")

</div>

\ in python is an escape character, you need to escape it `'c:\\...'`or use a raw string with `r'c:\... '`  
However you should have got an error?

> [@lrose](#):
>
> Nick was/is using PyCharm

Definitely still is 🙂 it's very charming. I find it far better than vs code for python. Whoever writes these smart IDEs must be gods. The amount of work that goes into them has to be insane

Edit:

> [@lrose](#):
>
> but I would expect any Python 3.1+ environment would be able to run it

as of my original writing, the `littletree` library requires min Python 3.10 to run

---

<div class="post-metadata">

**Author:** ![Jeff\_Krol](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jeff_krol/32/69426_2.png) [@Jeff\_Krol](https://forum.inductiveautomation.com/u/Jeff_Krol)\
**Post date:** [October 29, 2024, 4:19pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/12 "2024-10-29T16:19:52Z")

</div>

I'm trying to run this script now and I'm getting this error:

```python
  File "C:\Users\myUserName\Documents\Python Scripts\Ignition_JSON_TagDiff.py", line 73, in <module>
    compare_tag_json('C:\\Users\\myUserName\\Documents\\Projects\\Project1\\Tag Backups\\Unsorted\\tags_20241028.json','C:\\Users\\myUserName\\Documents\\Projects\\Projec2\\Tag Backups\\Unsorted\\tags_20241028.json')
    ~~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\myUserName\Documents\Python Scripts\Ignition_JSON_TagDiff.py", line 14, in compare_tag_json
    compare_tag_obj(original, modified, exclude_tags_in_removed_folders, exclude_tags_in_added_udt_instances)
    ~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\myUserName\Documents\Python Scripts\Ignition_JSON_TagDiff.py", line 22, in compare_tag_obj
    for diff_node in original_tree.compare(modified_tree).iter_tree():
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'iter_tree'

```

It looks like the objects have data in them. I have a space in my file path so I was worried that it wasn't opening the JSON files correctly, but when I use the debugger, I can see data in the original\_tree and modified\_tree objects. Any suggestions what might be causing this?

btw thanks for creating this code, I assumed doing a diff on two JSON tag exports would be simple, but everything is shuffled out of order.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [October 30, 2024, 12:11am UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/13 "2024-10-30T00:11:21Z")

</div>

I would need some tag exports to run this against to see what the errors are produced by, you can send via PM if you want. And also a copy of the script you're using, as mine wasn't compatible with running directly from the console.

I realise also that I'm missing docstrings... oops. I edited to add docstrings

---

<div class="post-metadata">

**Author:** ![Jeff\_Krol](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jeff_krol/32/69426_2.png) [@Jeff\_Krol](https://forum.inductiveautomation.com/u/Jeff_Krol)\
**Post date:** [October 31, 2024, 1:18pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/14 "2024-10-31T13:18:38Z")

</div>

That's very kind of you to offer that. I might take you up on that.

However, in the meantime, I ended up writing a Jython script that uses **system.tag.browse** with a recursive filter to get a list of tags (these are alphabetical!), and then I enumerate through the results, write the fullPath string to a JSON file, pass in the fullPath value from each result to **system.tag.exportTags** (with recursive set to false), and then write that string to the JSON file as well. There's a decent bit of formatting in the script to create a valid JSON file and generalize some of the data.

Essentially, I needed to compare two different projects' tags. In theory these two projects should be very similar but we needed to understand what differences exist. They are nearly identical, which is why I was surprised that just using "Tag Export" in the Designer was giving wildly different ordering. I'm not sure in what order they get exported. 🤷🏻‍♂️

---

<div class="post-metadata">

**Author:** ![Chris\_Bingham](https://avatars.discourse-cdn.com/v4/letter/c/e5b9ba/32.png) [@Chris\_Bingham](https://forum.inductiveautomation.com/u/Chris_Bingham)\
**Post date:** [October 31, 2024, 2:48pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/15 "2024-10-31T14:48:42Z")

</div>

Here's a link to another post showing the method I use (that thread has a few more elegant options to suit their needs as well):

> [@Json Diffing Discussion](https://forum.inductiveautomation.com/t/json-diffing-discussion/96800/35):
>
> My implementation of a dictionary & list deepSort() recursive function: \*Edit: Keys within dictionaries (and values within lists) are not 'sorted' (alphabetically & numerically ascending) but does appear to organize keys/values of two UDTs in the same order. Hmmm.... def deepSort(obj, ignoreKeys=[]): # Beta if isinstance(obj, list): result = [] for value in sorted(obj): if hasattr(value, '\_\_iter\_\_'): result.append(deepSort(value)) else: result.append(value) return result …

My procedure is as follows:

- From Script Console:
  - Get tag configs (see code from thread above)
  - Sort tag configs (see code from thread above)
  - Copy output of both configs (see code from thread above) to Notepad++, with each config in a different file/tab.

- For each file, within Notepad++:
  - Using _JSTool_ Add-on: JSFormat (Pretty-print JSON - even if it's not valid JSON)
  - Change Language back to None (from Language menu - basically, ignore all of the errors that the JSON interpreter throws)

- Then, using _Compare_ Add-on:
  - While File 1 is active, select "Set as First to Compare".
  - While File 2 is active, select "Compare".

A sample output is also in that thread, except, the comparison was not from a `system.tag.getConfiguration()` output.

That deepSort() function could probably be enhanced to utilize an OrderedDict, but has suited my needs so far.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [October 31, 2024, 8:14pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/16 "2024-10-31T20:14:31Z")

</div>

Just keep in mind that any diffing of tag json with your standard diff tools is going to have limited use-cases, ie reasonably simple cases. Even with that simple case you screenshotted here [Json Diffing Discussion - #36 by Chris\_Bingham](https://forum.inductiveautomation.com/t/json-diffing-discussion/96800/36), it's highlighting changes that shouldn't be there. A new tag was create called Donkey while another tag Detail was deleted, but diff tools tell you there are differences between those two tags. That's but am extremely simple example. I'm sure you can imagine the mess this could be with a much larger tag structure.

More details here [Json Diffing Discussion - #32 by nminchin](https://forum.inductiveautomation.com/t/json-diffing-discussion/96800/32)

---

<div class="post-metadata">

**Author:** ![Nick\_Wilson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nick_wilson/32/80663_2.png) [@Nick\_Wilson](https://forum.inductiveautomation.com/u/Nick_Wilson)\
**Post date:** [November 1, 2024, 1:38pm UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/17 "2024-11-01T13:38:02Z")

</div>

I like to use the VSCode extension "Compare Folders" for this. I'll typically compare the root directory of my "staging" project with the root directory of the active "production" project.

![92312411-f1f3b600-efc8-11ea-93b8-e90a3e25e9cb](https://us1.discourse-cdn.com/inductiveautomation/original/3X/3/1/311407d9dd4cb5d17f7491733c37394564ee52fa.gif)

---

<div class="post-metadata">

**Author:** ![lucaci.ed](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lucaci.ed/32/63789_2.png) [@lucaci.ed](https://forum.inductiveautomation.com/u/lucaci.ed)\
**Post date:** [January 3, 2025, 8:43am UTC](https://forum.inductiveautomation.com/t/json-compare-tool-for-views-and-tags/41163/18 "2025-01-03T08:43:51Z")

</div>

Why don't you share it? It would be useful to many people!
