Narrowing a wide JSON parameter array via script transform

v8.1.28, Perspective

I have a view parameter bound to a named query returning JSON. This is a fairly wide query (50+ fields) returning 1 to 50 records typically. On this view, I have a flex repeater, to which I want to pass only a handful of fields from each record. So if I my query result is 54 columns by 23 rows, I want to pass something like 4 columns by 23 rows to the flex repeater.

I've mocked this up as shown below (I'm only using 3 fields per record here but in reality there are over 50):
View parameters:
sample_parameters

Flex repeater binding:
sample_binding

The flex repeater instances property is bound to view.params.ds. In this mockup, I'm wanting to strip out the "name" field from each record and pass only "id" and "loc".

My transform script is as follows:

def transform(self, value, quality, timestamp):
	cols=["id","loc"]
	result=[]
	for row in value:
		for key in row.keys():
			if key not in cols:
				del row[key]
		result.append(row)
	return value

It's choking on del row[key].

I'm guessing I could return the query as a dataset instead of JSON, but that tends to obscure the data and make it more difficult to work with in dev. Hoping this is just a simple syntax mistake...

What's the exception/error?
You might be able to get around this by reversing the logic, and inserting only the keys you want.

def transform(self, value, quality, timestamp):
	cols=["id","loc"]
	result=[]
	for row in value:
		for key, value in row.items():
            row_dict = {}
			if key in cols:  # swap logic here
				row_dict[key] = value
		    result.append(row_dict)
	return result

Nice use case for a nested comprehension:

def transform(self, value, quality, timestamp):
	cols = {"id", "loc"}

	return [
		{
			key: value
		} for key, value in row.items() if key in cols
		for row in value
	]

yeah... "nice"...

2 Likes

As long as your requirements never need to substantially change, and you don't mind the weird control flow, and you squint hard enough, it reads basically just like JSON!

I'm fully in favor of proper sequential transforms of data. Python's useless lambdas make that basically impossible, so embracing "idiomatic" Python, regardless of personal opinions, is still probably the best move. :man_shrugging:

1 Like

I'd use an expression:

forEach(
	{view.params.ds},
	asMap(
		'id', it()['id'],
		'name', it()['name'],
		'loc', it()['loc']
	)
)

The functions forEach() and asMap() are provided by my Integration Toolkit module.

The above will also work, unchanged, if view.params.ds is a dataset. Which I highly recommend, for performance. (And set it private, so it never ties up your websocket.)

DotReferenceJythonMap object doesn't support item deletion.

Tried your sample script and I'm getting a "Parse Error in script transform" on row_dict = {}.

(by the way, how do you copy/paste an error message when it's only visible in the tooltip when you hover over the Error_Configuration script result in the binding dialog?)

This post talks about having to re-map a JythonMap into an array of dictionaries due to chained transforms. I only have one single transform here, but I tried re-mapping the JythonMap object and that seems to work:

	value = [dict(x) for x in value[:]]
	cols = ["id","loc"]
	result=[]
	for row in value:
		for key in row.keys():
			if key not in cols:
				del row[key]
		result.append(row)
	return value

So before I move on, I'm curious if this is the best solution or if there's something else I should be doing to avoid the need to remap (ok, use a dataset result from the query, yeah I know, but is that all?).

In this case, I'm getting "UnboundLocalError: local variable 'row' referenced before assignment" occurring at

} for key, value in row.items() if key in cols

I tried

	row=value[0]
	return [
		{
			key: value
		} for key, value in row.items() if key in cols
		for row in value
	]

and that results in "TypeError: 'long' object is not iterable"

and I tried initializing row with row={}, and that just returns an empty list.

No, transforms (and expression bindings) are expected to take one or more input values and produce a result to deliver. Input values are to be treated as immutable (because they usually are).

If you don't want to remap your object, do the initial conversion of dataset to list of dictionaries with just the desired keys.

I highly recommend never using JSON returns from a query binding. Under the hood, all queries return datasets. If you select JSON output, there will be a dataset-to-list-of-dictionaries conversion step inserted. If you aren't going to use that JSON exactly as-is, it is better to return the dataset and separately transform to the precise list of dictionaries needed.

And if you can avoid using jython entirely, you'll be substantially faster/more efficient yet. {** cough ** Integration Toolkit ** cough **}.

Also, mark any custom properties that are used as intermediate values as private. Especially datasets and large lists. This greatly reduces websocket traffic.

I believe Paul made an error and inverted the order in the comprehension.

Try

cols = {"id", "loc"}

return [
	{
		key: value
	} for row in value
	for key, value in row.items() if key in cols
]
1 Like

And the exception from my code was likely a formatting difference between the forums and the script editor. If you right-click within the script editor, you can elect to view whitespace. Verify all the white space is of the same type, either all spaces, or all indents. Examining this myself, it looks like line 9 will need the same treatment. Always be cautious when pasting from the forums because code written within the forum doesn't have tabs because browser reasons.
Screenshot 2024-02-24 at 3.48.44 PM

Would it be possible to change the appearance of the code blocks on the forums to give some indication like in your screenshot ?

I have a parent view with several different child views that are each driven by a subset of the dataset bound to the parent view. One or two of the child views will use the majority of the parent's dataset, and there's also a flex repeater that is driven by the parent's dataset. When returning the query as JSON, it was easy to bind that to the flex repeater...now I need to convert the jsonDataset object to the list of dictionaries for a flex repeater. Is there no built-in function to do this? What's the most efficient way?

I do like the looks of your Integration Toolkit...however my client is lukewarm on 3rd-party extensions for now unfortunately.

I would use my toolkit's where() expression function to partition in dataset format for the nested uses. Or nest that in another forEach() to deliver the customized list-of-dictionaries to each part.

My condolences. For you. I have no sympathy for end-users who handcuff their developers use a of a modular platform.

1 Like