# Expression Structure Script to Replace Spaces with Underscore

**URL:** <https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997>\
**Category:** Ignition\
**Tags:** perspective\
**Created:** [September 15, 2024, 6:50pm UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997 "2024-09-15T18:50:16Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Ly17](https://avatars.discourse-cdn.com/v4/letter/l/c57346/32.png) [@Ly17](https://forum.inductiveautomation.com/u/Ly17)\
**Post date:** [September 15, 2024, 6:50pm UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997/1 "2024-09-15T18:50:16Z")

</div>

I have the following transform script but it's only replacing spaces with underscores at the top level of the object structure and not any of the sub-levels.

```python
def transform(self, value, quality, timestamp):
    # Create a new dictionary to store the transformed keys
    transformed_value = {}

    # Define a helper function to recursively transform the keys
    def transform_keys(dictionary):
        # Iterate through each key-value pair in the dictionary
        for key, val in dictionary.items():
            # Replace spaces with underscores in the key
            new_key = key.replace(" ", "_")
            
            # Check if the value is a dictionary
            if isinstance(val, dict):
                # Recursively transform the sub-dictionary
                transformed_value[new_key] = transform_keys(val)
            else:
                # Add the new key and corresponding value to the transformed dictionary
                transformed_value[new_key] = val

    # Call the helper function to transform the keys
    transform_keys(value)
    
    # Return the transformed dictionary
    return transformed_value

```

Any thoughts on what the issue is or how to modify this to evaluate correctly through all the sub-levels and replace spaces with underscores?

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [September 15, 2024, 6:58pm UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997/2 "2024-09-15T18:58:38Z")

</div>

You're close, just move `transformed_value` inside your inner function and return it at the end.

```python
def transform_keys(dictionary):
    transformed_value = {}
    
    for key, val in dictionary.items():
        new_key = key.replace(" ", "_")
        
        if isinstance(val, dict):
            transformed_value[new_key] = transform_keys(val)
        else:
            transformed_value[new_key] = val

    return transformed_value

```

Then at the end of the script:

```python
return transform_keys(value)

```

---

<div class="post-metadata">

**Author:** ![Ly17](https://avatars.discourse-cdn.com/v4/letter/l/c57346/32.png) [@Ly17](https://forum.inductiveautomation.com/u/Ly17)\
**Post date:** [September 15, 2024, 9:52pm UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997/3 "2024-09-15T21:52:41Z")

</div>

> [@Ly17](#):
>
> ```python
> # Call the helper function to transform the keys
> transform_keys(value)
> 
> ```

Here is the updated script but it didn't change anything.

```python
def transform(self, value, quality, timestamp):
        
    # Define a helper function to recursively transform the keys
    def transform_keys(dictionary):
        # Create a new dictionary to store the transformed keys
        transformed_value = {}
        
        # Iterate through each key-value pair in the dictionary
        for key, val in dictionary.items():
            # Replace spaces with underscores in the key
            new_key = key.replace(" ", "_")
            
            # Check if the value is a dictionary
            if isinstance(val, dict):
                # Recursively transform the sub-dictionary
                transformed_value[new_key] = transform_keys(val)
            else:
                # Add the new key and corresponding value to the transformed dictionary
                transformed_value[new_key] = val
        
        # Return the transformed dictionary
        return transformed_value
    
    # Call the helper function to transform the keys
    return transform_keys(value)

```

This is the current result:  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/2/4/24c0f3165ff4ec6712f4f5ce0243661e8e266b3f.png)  
The keys under Hours didn't change.

---

<div class="post-metadata">

**Author:** ![Kevin.Herron](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/kevin.herron/32/7259_2.png) [@Kevin.Herron](https://forum.inductiveautomation.com/u/Kevin.Herron)\
**Post date:** [September 15, 2024, 10:28pm UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997/4 "2024-09-15T22:28:39Z")

</div>

> [@Ly17](#):
>
> `if isinstance(val, dict):`

Hmm, maybe if you did something like

```python
if val.isMappingType():

```

instead.

guessing that whatever these inner objects are fail the `isinstance` check against built-in `dict`.

---

<div class="post-metadata">

**Author:** ![Ly17](https://avatars.discourse-cdn.com/v4/letter/l/c57346/32.png) [@Ly17](https://forum.inductiveautomation.com/u/Ly17)\
**Post date:** [September 16, 2024, 2:29am UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997/5 "2024-09-16T02:29:41Z")

</div>

It didn't work.

---

<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:** [September 16, 2024, 3:56am UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997/6 "2024-09-16T03:56:47Z")

</div>

I'd be printing the key and the type of val here

 ![Screenshot_20240915_205431_Chrome](https://us1.discourse-cdn.com/inductiveautomation/original/3X/2/2/2291f511848685a545e451511c67767970723326.jpeg)

Eg

```python
system.perspective.print('{} {}'.format(key, type(val)) 

```

---

<div class="post-metadata">

**Author:** ![Ly17](https://avatars.discourse-cdn.com/v4/letter/l/c57346/32.png) [@Ly17](https://forum.inductiveautomation.com/u/Ly17)\
**Post date:** [September 16, 2024, 5:41am UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997/7 "2024-09-16T05:41:14Z")

</div>

Figured it out. Those objects must not register as python dictionaries when passed into the transform() function, so I used this instead.

> if hasattr(val, 'items'):

Thanks,

---

<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:** [September 16, 2024, 7:14am UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997/8 "2024-09-16T07:14:18Z")

</div>

I'll suggest making a function that transform keys into something that ignition likes and putting it in your library.  
I use this:

```python
import re

from unicodedata import normalize, combining

class MalformedKeyError(Exception):
	pass

def remove_accents(s):
	# NFKD: https://fr.wikipedia.org/wiki/Normalisation_Unicode#NFKD
	nfkd_form = normalize('NFKD', s)
	return "".join(c for c in nfkd_form if not combining(c))

def make_key(s):
	"""
	Sanitize a string so it can be used as a key in an ignition object.
	
	If the first character is a digit, an underscore is added before it
	accents are removed
	leading and trailing whitespaces are removed
	other whitespaces are replaced with an underscore
	"""
	if isinstance(s, str):
		s = s.decode('utf-8')
	key = s.strip().lower()
	if not key:
		raise MalformedKeyError("Can't sanitize {!r} into a valid key".format(s)) 
	if key[0] in "0123456789":
		key = '_' + key
	return re.sub('\W+', '_', remove_accents(key))

```

But maybe it's overkill for your use case ?

I'd also put the "transform\_keys" function in a script library, this way it won't have to be redefined every time the transform runs, and it's a useful function anyway that you might want to reuse at some point.

Here's what the whole thing would look like, with a slight rewrite of the inner function and a simple `replace` to keep things simple:

in a `custom_lib` script package:

```python
def make_key(s):
	return s.replace(' ', '_')

def sanitize_dict_keys(dic):
	return {
		make_key(k): sanitize_dict_keys(v) if hasattr(v, 'items') else v
		for k, v in dic.iteritems()
	}

```

in your transform:

```python
def transform(self, value, quality, timestamp):
	return custom_lib.sanitize_dict_keys(value)

```

edit:  
This actually seemed like a useful enough function, so I added it to my own library:

```python
def sanitize_dict_keys(dic, sfunc=make_key, recursive=True):
	"""
	Sanitize a dictionary keys.
	
	params:
		dic: Dictionnary that will be sanitized
		sfunc: A function that takes a string and returns a string, will be passed the keys of the dictionnary
		recursive:	A boolean that indicates wheter to transform the keys of nested dictionnaries or only the root one. default True
	
	return:
		The sanitized dictionnary.
	"""
	return {
		sfunc(k): sanitize_dict_keys(v, sfunc, recursive) if hasattr(v, 'items') and recursive else v
		for k, v in dic.iteritems()
	}

```

---

<div class="post-metadata">

**Author:** ![Ly17](https://avatars.discourse-cdn.com/v4/letter/l/c57346/32.png) [@Ly17](https://forum.inductiveautomation.com/u/Ly17)\
**Post date:** [September 17, 2024, 4:18am UTC](https://forum.inductiveautomation.com/t/expression-structure-script-to-replace-spaces-with-underscore/95997/9 "2024-09-17T04:18:17Z")

</div>

Thanks, I appreciate it. That will help a lot.
