Get/set JSON value where the JSON path to it is flexible

Hello fellow Maestros :slight_smile:

I have a JSON file that contains (among other things) the following:

{
	"ProbenartenNummer" : {
		"11465/12" : {
			"Bio" : "Nein",
			"Datenbank" : {
				"HDE" : false,
			},
		},
	},
},

My problem is that the "11465/12" number can be something different, like "11345/12" or "10234/09".

The proper way to get to the JSON value of the HDE entry would be something like

%JSONValue%instance_JSON_UTF8Content.ProbenartenNummer.11465/12.Datenbank{HDE}%

Right?
However, since the number can change, I tried to capture it first using a RegEx and getting it into a variable, then place the variable inside the JSONValue variable, but I can't get that to work.

Plus, of course, there is that darn "/12" inside the JSONValue path, which could further complicate things.

How do I get and set the value of HDE when the path towards the value can change because of that number code?

Any idea is very welcome!

JS or jq are probably what you need.

But a secondary problem is that your example is not parseable as JSON โ€“
JSON does not allow for trailing commas, of which you show several.

(Try an online JSON validator)

If you had valid JSON (I've pruned the non-JSON commas from your source, below), you might be able use something like the function in the macro below.

(But JSON parsing would simply fail with the example which you show above โ€“ trailing commas are valid in JS source, but not in JSON.

Not sure how your source is being formed, but if you can parse it in a JavaScript interpreter to an JS Object, then you would be able to obtain genuine JSON with JSON.stringify(obj);

Any value found for nested key in JSON.kmmacros (4.8 KB)


Expand disclosure triangle to view JS source
const main = () =>
    either(
        message => message
    )(
        valueFound => valueFound
    )(
        bindLR(
            jsonParseLR(kmvar.local_JSON)
        )(
            atDeepKeyLR(kmvar.local_Key)
        )
    );


// ---- FIRST VALUE FOUND AT POSSIBLY NESTED KEY -----

// atDeepKeyLR :: String -> Dict -> Either String a
const atDeepKeyLR = k =>
    // Just the first value found at a matching key at
    // any level of a possibly nested object, or
    // Nothing if no value is found.
    dict => {
        const f = acc => subKey => obj => {
            const v = obj[subKey];

            return "object" === typeof v
                ? go(v)
                : acc;
        };

        const go = obj =>
            k in obj
                ? Right(obj[k])
                : Object.keys(obj).reduce(
                    (a, subKey) =>
                        "Right" in a
                            ? a
                            : f(a)(subKey)(obj),
                    Left(`"${k}" not found in:\n${JSON.stringify(dict, null, 2)}`)
                );

        return go(dict);
    };


// --------------------- GENERIC ---------------------


// Left :: a -> Either a b
const Left = x => ({
    type: "Either",
    Left: x
});

// Right :: b -> Either a b
const Right = x => ({
    type: "Either",
    Right: x
});


// bindLR (>>=) :: Either a ->
// (a -> Either b) -> Either b
const bindLR = lr =>
    // Bind operator for the Either option type.
    // If lr has a Left value then lr unchanged,
    // otherwise the function mf applied to the
    // Right value in lr.
    mf => "Left" in lr
        ? lr
        : mf(lr.Right);


// either :: (a -> c) -> (b -> c) -> Either a b -> c
const either = fl =>
    // Application of the function fl to the
    // contents of any Left value in e, or
    // the application of fr to its Right value.
    fr => e => "Left" in e
        ? fl(e.Left)
        : fr(e.Right);


// jsonParseLR :: String -> Either String a
const jsonParseLR = s => {
    try {
        return Right(JSON.parse(s));
    } catch (e) {
        return Left(
            [
                e.message,
                `(line:${e.line} col:${e.column})`
            ].join("\n")
        );
    }
};

// MAIN ---
return main();
2 Likes

Oh wow, are you a wizard?

It works to fetch the value! Not sure about also setting the value (writing it back into the json data), but this is a great start for me to explore!

And here I am always thinking about programming stuff and then I see your code and think "I'll never create something like this" :smiling_face_with_tear:

Thank you so much!

fetch the value

With jq a simple query like

.ProbenartenNummer[].Datenbank.HDE

would suffice to get the value of the HDE key.

As for:

To obtain an adjusted copy of your input JSON data you could install jq from:


(assuming that you have already installed HomeBrew)


Once jq is installed you can check its full path in Terminal.app using the incantation:

which jq

If it turns out to be, for example at:

/opt/homebrew/bin/jq

Then you could use that path in a macro like this:

Updated copy of JSON value.kmmacros (2.3 KB)

1 Like

Or illustrating the lookup and the update together:

Initial value of inner key- and updated copy of JSON.kmmacros (5.4 KB)

1 Like

Can you also use a JSON Keys collection? Though that might be particular to this case, where there's only one key at the level we want!

Demo of both getting and setting:

Get and Set for Variable Key.kmmacros (5.7 KB)

1 Like