Jump to content

Help improving a JSON file prettifier workflow?


Recommended Posts

I probably spent far too much time not knowing what I was doing to produce this workflow but now that it will prettify a single obfuscated .json file, I could use some help with improvements:

 

  1. Support prettifying multiple selected files at a time (I've been able to do this with other languages, but haven't figured out how to work with an array of files in python in a Script Filter)
  2. Ignore any non-JSON files that might be passed in (currently if you run this against a TXT file, it'll replace the file contents with {query}! (whoops))
  3. Improve the notifications based on whether a single or multiple files were processed

 

I did take a look at @deanishe's Workflow Library or Python but it seemed like overkill for what I wanted to do.

 

Also, I did find several other workflows that will prettify the clipboard's contents, but I specifically want to just handle files in Finder (for now). 

 

I found this technique which looked promising, but I kept getting syntax errors in the Script Filter when I would run it:

 

import json
import os

# Read JSON data from file and pretty print it

with open("{query}", "r") as jsonfile:

    # Convert JSON file to Python Types
    obj = json.load(jsonfile)

    # Pretty print JSON data
    pretty_json = json.dumps(obj, indent=4)
    print(pretty_json)

 

Edited by Chris Messina
added code sample
Link to comment
6 hours ago, Chris Messina said:

Support prettifying multiple selected files at a time

 

Your workflow's set up the wrong way for that. A File Action can output an array, and a script can accept multiple arguments (provided you use argv), but most other elements can't handle multiple inputs and will squash them into a single string. Write Text File can only write one file, so if you need to handle multiple files, you have to do it in code. Like this.

Edited by deanishe
Link to comment
10 hours ago, deanishe said:

Write Text File can only write one file, so if you need to handle multiple files, you have to do it in code. Like this.

 

Amazing, thank you! I've updated my workflow and given you credit. Appreciate it!

 

How might I add a check to make sure the selected files are actually JSON files before processing them?

Link to comment
1 hour ago, Chris Messina said:

How might I add a check to make sure the selected files are actually JSON files before processing them?

 

Your File Filter doesn't accept anything else.

 

EDIT: More generally, a separate validity check is pointless in a situation like this because decoding the file to process it is a validity check. You should just go ahead and try to process all the files, but handle each one in a try … except clause in your main loop, so one invalid file doesn’t prevent others from being processed.

Edited by deanishe
Link to comment
1 hour ago, deanishe said:

Your File Filter doesn't accept anything else.

 

Right, but the keyboard shortcut bypasses that check — which is how I accidentally replaced the contents of an XML file with the path of the file... however, I just tried this again with your method and it does seem like the function won't run if it doesn't find a suitable JSON object ("No JSON object could be decoded") so I see your point! 

 

I will work on the try … except clause in my main loop though!

Link to comment
45 minutes ago, Chris Messina said:

which is how I accidentally replaced the contents of an XML file with the path of the file...

 

Right yeah, good point: workflows keep running when a Run Script action fails, so your original version also needs some kind of hand-rolled error check before the Write Text File.

 

But the script is inherently fail safe because it will error out decoding the file and never run the code that overwrites it.

Link to comment
10 minutes ago, deanishe said:

But the script is inherently fail safe because it will error out decoding the file and never run the code that overwrites it.

 

Here's what I came up with — seems to work:

 

import json
import sys

for path in sys.argv[1:]:
    with open(path) as fp:
        data = json.load(fp)

    with open(path, 'w') as fp:
        try:
            json.dump(data, fp, indent=4, separators=(',', ': '))
        except TypeError:
            pass

 

How might I add a file count which gets passed to a notification? I can probably figure out the count, but I always struggle with how to pass a variable from a Script Filter to another block. 

Link to comment

Your try ... except is in the wrong place: the script will fail on read 99.9% of the time, not write. But in any case, you generally just put it around the entire per-item code:

 

from __future__ import print_function
import json
import sys

n = 0  # number of files processed
for path in sys.argv[1:]:
    try:
        with open(path) as fp:
            data = json.load(fp)

        with open(path, 'w') as fp:
            json.dump(data, fp, indent=4, separators=(',', ': '))

        n += 1
    except Exception as err:
        # anything written to STDERR goes to Alfred's debugger
        print('[error] file %s: %s' % (path, err), file=sys.stderr)

plural = ('s', '')[n==1]
# anything written to STDOUT goes to the next action (i.e. arg/{query})
print('Prettified %d file%s' % (n, plural), end='')

 

40 minutes ago, Chris Messina said:

I always struggle with how to pass a variable from a Script Filter to another block.

 

Variables and arg/{query} are two different matters, and Script Filters and regular Run Script actions are two different things again.

 

In this case, you don’t really need to use variables, because you only need one output: the notification. So you can just print the notification text because anything you print becomes the next element’s arg/{query}.

 

Important: If you’re going to get into writing Python, you should stop using /usr/bin/python and use /usr/bin/python3 instead. (You’ll have to use Alfred's External File option for Language and the shebang #!/usr/bin/python3 to do that.) Python 2 is dead and will be gone soon, and Python 3 is easier to use, anyway.

Edited by deanishe
Link to comment
Quote

you should stop using /usr/bin/python and use /usr/bin/python3 instead.

@deanisheI am trying to migrate my scripts to Python3 and I get this error below from your Workflow, what am I doing wrong? my python3 version should be 3.9.4.

thanks!

File "[...].py", line 12, in <module>
    from workflow import Workflow3, ICON_WEB, web
  File "[...]/src/workflow/__init__.py", line 16, in <module>
    from .workflow import Workflow, manager
  File "[...]/src/workflow/workflow.py", line 25, in <module>
    import cPickle
ModuleNotFoundError: No module named 'cPickle'

 

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...