Need help breaking down JSON array into distinct categories

I’m looking to create a scraper that scrapes my gmail account once a day getting multiple listings from a single email. I’ve managed to set up a workflow that extracts the listings as a JSON array like this (this is an example of a single email):
[\n {\n \“job_title\”: \“SWM-2026-01 - Development of Municipal Wayfinding and Community Signage Strategy\”,\n \“organization\”: \“County of Middlesex\”,\n \“deadline\”: \“02-27-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“05-PRC-2026 - Washroom Renovation - Victoria Park\”,\n \“organization\”: \“Town of Cobourg\”,\n \“deadline\”: \“03-10-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“26-T15 - Engineering Services for Heat Pump Replacement at Various Housing Services Division Properties\”,\n \“organization\”: \“City of Cornwall\”,\n \“deadline\”: \“03-11-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“RFPQ-2026-051 - Elgin Terminal Reservoir Expansion\”,\n \“organization\”: \“City of London\”,\n \“deadline\”: \“03-12-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“2026-004 - Arboriculture Services Preferred Supplier Agreement\”,\n \“organization\”: \“Sturgeon County\”,\n \“deadline\”: \“03-12-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“FIN2026-076Q - 2026 Pavement Management Contract Administration and Contract Inspection Services\”,\n \“organization\”: \“City of Barrie\”,\n \“deadline\”: \“03-17-2026\”,\n \“link\”: null\n },\n {\n \“job_title\”: \“P26-004 - Insurance Broker of Record\”,\n \“organization\”: \“City of Prince George\”,\n \“deadline\”: \“03-19-2026\”,\n \“link\”: null\n }\n]"

However, I’m having some difficulty splitting up each array into individual listings that populate into a google sheet. I’m pretty new to all this so any help or advice is greatly appreciated!

Here are my workflow and code for the Parse Job Listings node:

// 1) Grab the AI output from the incoming item
const raw = $input.first().json?.[0] ?? $input.first().json ?? $input.first().json?.text ?? $input.first().json?.output;

// 2) Normalize to string
let text = (typeof raw === ‘string’) ? raw : JSON.stringify(raw ?? ‘’);

// 3) Strip markdown code fences (json ... )
text = text
.replace(/json\s*/gi, '') .replace(//g, ‘’)
.trim();

// 4) Extract the most likely JSON chunk (array preferred)
function extractJsonCandidate(s) {
const a0 = s.indexOf(‘[’);
const a1 = s.lastIndexOf(‘]’);
if (a0 !== -1 && a1 !== -1 && a1 > a0) return s.slice(a0, a1 + 1);

const o0 = s.indexOf(‘{’);
const o1 = s.lastIndexOf(‘}’);
if (o0 !== -1 && o1 !== -1 && o1 > o0) return s.slice(o0, o1 + 1);

return s;
}

const candidate = extractJsonCandidate(text);

// 5) Parse with a small fix for trailing commas
function parseLooseJson(s) {
try {
return JSON.parse(s);
} catch (e) {
// remove trailing commas before } or ]
const fixed = s.replace(/,\s*([}]])/g, ‘$1’);
return JSON.parse(fixed);
}
}

let parsed;
try {
parsed = parseLooseJson(candidate);
} catch (error) {
// If parsing fails, return one item with error info (won’t break the workflow)
return [{
json: {
error: ‘Failed to parse AI output as JSON’,
raw_output: text,
message: error.message
}
}];
}

// 6) Unwrap common wrapper shapes: { output: … } or { “0”: … }
function unwrap(v) {
let x = v;

// sometimes output itself is a JSON string
if (typeof x === ‘string’) x = parseLooseJson(extractJsonCandidate(x));

// unwrap a few layers if needed
for (let i = 0; i < 3; i++) {
if (x && typeof x === ‘object’ && !Array.isArray(x)) {
if (x.output) { x = x.output; continue; }
if (x.jobs) { x = x.jobs; continue; }
if (x.data) { x = x.data; continue; }
if (x[“0”]) { x = x[“0”]; continue; }
}
break;
}

return x;
}

let jobs = unwrap(parsed);

// 7) Ensure array
if (!Array.isArray(jobs)) jobs = [jobs];

// 8) Map to ONLY the 3 fields required by Google Sheets node
// Support multiple possible key names from your AI: job_title, job title, title, etc.
function pickJobTitle(j) {
return j?.job_title ?? j?.jobTitle ?? j?.[‘job title’] ?? j?.title ?? j?.job_name ?? ‘’;
}

function pickOrganization(j) {
return j?.organization ?? j?.org ?? j?.agency ?? j?.company ?? ‘’;
}

function pickDeadline(j) {
return j?.deadline ?? j?.due_date ?? j?.dueDate ?? j?.closing_date ?? ‘’;
}

return jobs
.filter(j => j && typeof j === ‘object’)
.map(j => ({
json: {
job_title: String(pickJobTitle(j)).trim(),
organization: String(pickOrganization(j)).trim(),
deadline: String(pickDeadline(j)).trim()
}
}));

1 Like

Hi @pnanos Welcome to the community!

Using Split node can be a good take and straightforward add a Split Out node right after Parse Job Listings and leaving include as no other field, let me know if that works

1 Like

Hello,

If your code ends in something like this:

return jobs.map(j => ({ json: { job_title: ..., organization: ..., deadline: ... } }));

You likely don’t need anything else. That outputs one item per listing so GSheets will expect that. Connect directly to GSheets by choosing Append Row. When running your code node you can check the output which should show multiple items “(Item 0, Item 1, Item 2, etc.)”, not one item containing an array.

1 Like