COMMENT
JSON ←→ dictionary action conversion tool
2021-09-08 v2.2.2 u/gluebyte
https://routinehub.co/shortcut/4626
MATHS
Random number between
1
and
2

Random Number
SCRIPTING
If
Random Number
is
1
SCRIPTING
Get current IP address
Options Under Construction

Current IP Address
SCRIPTING
If
Current IP Address
has any value
NETWORK
Get contents of
https://updatecheck.gluebyte.workers.dev/?id=5CC37B10
Options Under Construction

Contents of URL
SCRIPTING
If
Contents of URL
does not contain
2021-09-08
SCRIPTING
Choose from Menu

Menu Result
SCRIPTING
🌏 Go to RoutineHub

Menu Result
SAFARI
Open
https://routinehub.co/shortcut/4626/
SCRIPTING
Exit shortcut with
Result
SCRIPTING
👾 Download Update

Menu Result
NETWORK
Get contents of
https://routinehub.co/api/v1/shortcuts/4626/versions/latest
Options Under Construction

Contents of URL
WEB
Expand
https://routinehub.co/download/
Contents of URL

Expanded URL
DOCUMENTS
Replace
^.+icloud.com/
with
shortcuts://
in
Expanded URL

Updated Text
SAFARI
Open
Updated Text
SCRIPTING
Exit shortcut with
Result
SCRIPTING
⮐ Not Now

Menu Result
SCRIPTING
End Menu

Menu Result
SCRIPTING
End If

If Result
SCRIPTING
End If

If Result
SCRIPTING
End If

If Result
SCRIPTING
Choose from Menu

Menu Result
SCRIPTING
📋 Read JSON from Clipboard

Menu Result
NUMBER
1

Number
SCRIPTING
📝 Open JSON or Text File

Menu Result
NUMBER
2

Number
SCRIPTING
⌨️ Enter JSON Text

Menu Result
NUMBER
3

Number
SCRIPTING
🥢 Extract JSON from Shortcut

Menu Result
NUMBER
4

Number
SCRIPTING
🖇 Merge Dictionary Actions

Menu Result
NUMBER
5

Number
SCRIPTING
End Menu

Menu Result
SCRIPTING
If
Menu Result
is
1
SHARING
Get clipboard

Clipboard
SCRIPTING
End If

If Result
SCRIPTING
If
Menu Result
is
2
DOCUMENTS

File
SCRIPTING
Set name of
File
to
File
.txt

Renamed Item
SCRIPTING
End If

If Result
SCRIPTING
If
Menu Result
is
3
SCRIPTING

Provided Input
SCRIPTING
End If

If Result
SCRIPTING
If
Menu Result
is greater than
3
SHORTCUTS
Run
Shortcut Source Helper

Shortcut Result
SCRIPTING
If
Menu Result
is
4
SCRIPTING
Get file of type
public.json
from
Shortcut Result

File of Type
SCRIPTING
Choose from Menu

Menu Result
SCRIPTING
Standard (tabs)

Menu Result
TEXT
'\t'

Text
SCRIPTING
Compact (3 spaces)

Menu Result
TEXT
3).replace(/( +|^)\{\n +/g,'$1{ '

Text
SCRIPTING
End Menu

Menu Result
TEXT
const out = [];
for (const a of actions) {
if (a.WFWorkflowActionIdentifier === 'is.workflow.actions.dictionary' && (items = a.WFWorkflowActionParameters.WFItems)) {
out.push(getdict(items.Value));
} else if (a.WFWorkflowActionIdentifier === 'is.workflow.actions.list' && (items = a.WFWorkflowActionParameters.WFItems) && JSON.stringify(a).includes('WFDictionaryFieldValueItems')) {
out.push(items.map(getvalue));
}
}
document.write(out.length ? encodeURIComponent(out.map(d=>JSON.stringify(d,null,
Menu Result
)).join('\n\n')):'.');
function getdict(dict) {
const out = {};
for (const d of dict.WFDictionaryFieldValueItems) {
out[d.WFKey.Value.string] = getvalue(d);
}
return out;
}
function getvalue(d) {
if (typeof d !== 'object') { return d; }
let v = d.WFValue.Value;
switch (d.WFItemType) {
case 0: return getstr(v);
case 1: return getdict(v.Value);
case 2: return v.map(getvalue);
case 3: return v.attachmentsByRange ? `NUMBER(${getstr(v)})` : Number(v.string);
case 4: return v.Value ? `BOOLEAN(${varname(v.Value)})` : v;
}
}
function getstr(v) {
let str = v.string;
Object.entries(v.attachmentsByRange ?? {})
.sort((a,b) => parseInt(a[0].match(/\d+/)[0])-parseInt(b[0].match(/\d+/)[0]))
.forEach(a => { str = str.replace('\ufffc', `❰${varname(a[1])}❱`); });
return str;
}
function varname(v) {
return v.OutputName ?? v.VariableName ?? v.Type ?? v;
}

Text
URL
data:text/html;charset=utf-8,<script>const actions =
File of Type
.WFWorkflowActions;
Text
</script>

URL
SCRIPTING
Get file of type
com.apple.webarchive
from
URL

File of Type
SCRIPTING
If
File of Type
is
.
SCRIPTING
Show
No Dictionaries Found.
SCRIPTING
Exit shortcut with
Result
SCRIPTING
End If

If Result
SCRIPTING
URL
Decode
File of Type

URL Encoded Text
SCRIPTING
Set name of
URL Encoded Text
to
Shortcut Result
.txt

Renamed Item
SCRIPTING
If
WFInput?
is
<value>
DOCUMENTS
SCRIPTING
Otherwise
SCRIPTING
If
Renamed Item
is less than
<value>
DOCUMENTS
Show
Renamed Item
in Quick Look
SCRIPTING
End If

If Result
SCRIPTING
Choose from Menu

Menu Result
SCRIPTING
💻 Share

Menu Result
SHARING
Share
Renamed Item
SCRIPTING
📝 Save

Menu Result
DOCUMENTS

Saved File
SCRIPTING
📋 Copy to Clipboard

Menu Result
SHARING
Copy
Renamed Item
to clipboard
SCRIPTING
End Menu

Menu Result
SCRIPTING
End If

If Result
SCRIPTING
Exit shortcut with
Result
SCRIPTING
End If

If Result
SCRIPTING
End If

If Result
SCRIPTING
If
Menu Result
is
5
DOCUMENTS
Replace
</array>\s*</dict>\s*<key>WFSerializationType</key>\s*<string>WFDictionaryFieldValue</string>\s*</dict>\s*(?:</dict>\s*</dict>\s*<dict>\s*<key>WFWorkflowActionIdentifier</key>\s*<string>is.workflow.actions.dictionary</string>\s*[\s\S]{120,300}|<key>WFSerializationType</key>\s*<string>WFDictionaryFieldValue</string>\s*</dict>\s*</dict>\s*</array>\s*</dict>\s*</dict>\s*<dict>\s*<key>WFWorkflowActionIdentifier</key>\s*<string>is.workflow.actions.list</string>[\s\S]{270,450})<key>WFDictionaryFieldValueItems</key>\s*<array>\s*
with
World
in
If Result

Updated Text
SCRIPTING
Otherwise
SCRIPTING
Set
data
to
If Result
in
Dictionary

Dictionary
TEXT
let mode = 0;
let stack = [];
let output = '{"WFWorkflowActions":[';
let action = '';
let actioncount = 0;
while (buffer !== '') {
if (mode == 0) {
buffer = buffer.slice(buffer.search(/[\[\{]/));
let ch1 = buffer.charAt(0);
switch (ch1) {
case '{':
mode = 1;
action = '{"WFWorkflowActionIdentifier":"is.workflow.actions.dictionary","WFWorkflowActionParameters":{"WFItems":{"Value":{"WFDictionaryFieldValueItems":[';
break;
case '[':
mode = 2;
action = '{"WFWorkflowActionIdentifier":"is.workflow.actions.list","WFWorkflowActionParameters":{"WFItems":[';
break;
default:
buffer = '';
}
}
let ch1 = buffer.charAt(0);
buffer = buffer.slice(1);
buffer = buffer.slice(buffer.search(/\S|$/));
if (ch1 === ']' || ch1 === '}') {
if ((mode == 1 && ch1 === '}') || (mode == 2 && ch1 === ']')) {
if (stack.length == 0) {
output += action + ((mode == 1) ? ']},"WFSerializationType":"WFDictionaryFieldValue"}}},' : ']}},');
mode = 0;
action = '';
actioncount++;
} else {
action += (mode == 1) ? ']},"WFSerializationType":"WFDictionaryFieldValue"},"WFSerializationType":"WFDictionaryFieldValue"}},' : '],"WFSerializationType":"WFArrayParameterState"}},';
mode = stack.pop();
}
} else {
buffer = '';
}
} else {
let ch2 = buffer[0];
let key = '';
if (!']},'.includes(ch2)) {
let match;
if (mode == 1) {
match = buffer.match(/^"([^"\\]*(?:\\.[^"\\]*)*)"\s*:\s*/);
buffer = buffer.slice(match[0].length);
key = '"WFKey":{"Value":{"attachmentsByRange":{},"string":"' + match[1] + '"},"WFSerializationType":"WFTextTokenString"},';
ch2 = buffer[0];
}
switch (ch2) {
case '[':
case '{':
stack.push(mode);
if (ch2 === '{') {
mode = 1;
action += '{"WFItemType":1,' + key + '"WFValue":{"Value":{"Value":{"WFDictionaryFieldValueItems":[';
} else {
mode = 2;
action += '{"WFItemType":2,' + key + '"WFValue":{"Value":[';
}
break;
case '"':
match = buffer.match(/^"([^"\\]*(?:\\.[^"\\]*)*)"\s*/);
buffer = buffer.slice(match[0].length);
value = match[1];
action += (mode == 1) ? '{"WFItemType":0,' + key + '"WFValue":{"Value":{"attachmentsByRange":{},"string":"' + value + '"},"WFSerializationType":"WFTextTokenString"}},' : '"' + value + '",';
break;
default:
match = buffer.match(/^(.+?)\s*[,\]\}]/);
buffer = buffer.slice(match[0].length-1);
value = match[1];
if (value === 'true') {
action += '{"WFItemType":4,' + key + '"WFValue":{"Value":true,"WFSerializationType":"WFNumberSubstitutableState"}},';
} else if (value === 'false') {
action += '{"WFItemType":4,' + key + '"WFValue":{"Value":false,"WFSerializationType":"WFNumberSubstitutableState"}},';
} else if (value === 'null') {
action += (mode == 1) ? '{"WFItemType":0,' + key + '"WFValue":{"Value":{"attachmentsByRange":{},"string":""},"WFSerializationType":"WFTextTokenString"}},' : '';
} else {
let numStr = parseFloat(value).toString();
match = numStr.match(/(-?\d+)\.(\d+)/);
value = match ? parseInt(match[1] + match[2]) + 'E-' + match[2].length : numStr;
action += '{"WFItemType":3,' + key + '"WFValue":{"Value":{"attachmentsByRange":{},"string":"' + value + '"},"WFSerializationType":"WFTextTokenString"}},';
}
}
}
}
}
output += '],"WFWorkflowIcon":{"WFWorkflowIconStartColor":4292093695}}'
document.write(actioncount ? encodeURIComponent(output.replace(/\n/g, '\\n')) : '');

Text
URL
data:text/html;charset=utf-8,<script>let buffer =
Dictionary
.data;
Text
</script>

URL
SCRIPTING
Get file of type
com.apple.webarchive
from
URL

File of Type
SCRIPTING
URL
Decode
File of Type

URL Encoded Text
SCRIPTING
If
URL Encoded Text
begins with
{
SCRIPTING
Get file of type
com.apple.plist
from
URL Encoded Text

File of Type
SCRIPTING
Otherwise
SCRIPTING
SCRIPTING
Exit shortcut with
Result
SCRIPTING
End If

If Result
SCRIPTING
End If

If Result
SCRIPTING
Ask
New shortcut name:
Default Answer
[{'class': 'magic', 'value': 'Shortcut Result', 'glyph': '', 'UUID': 'CDC144E7-744E-4889-A3E7-BB3922452CC5'}, {'class': 'magic', 'value': 'File', 'glyph': '', 'UUID': '0A50002F-D882-41C9-867F-19B00739A5F0'}]

Provided Input
SCRIPTING
Set name of
If Result
to
Provided Input
.plist

Renamed Item
SHORTCUTS
Run
Shortcut Source Helper

Shortcut Result