// Apps Script

Destructure JSON from UrlFetchApp responses.

Learn how to safely destructure nested JSON from UrlFetchApp in Google Apps Script, using default empty-object patterns to prevent "Cannot destructure property of undefined" crashes when API fields are absent.

I want to pull values out of a UrlFetchApp JSON response without writing four lines of temp variables, but I keep crashing when the API omits a nested field.

The script

copy · paste · trigger
fetchUser.gs
Apps Script
// Safely destructure nested JSON from UrlFetchApp
function fetchUser(userId) {
  var url = 'https://api.example.com/users/' + userId;
  var response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
  var data = JSON.parse(response.getContentText());

  // Default {} at each nesting level so absent fields yield undefined,
  // not a thrown TypeError
  var { name, email, address: { city, country } = {} } = data;

  // Deeper nesting: role may be missing entirely
  var { profile: { role, plan } = {}, meta: { createdAt } = {} } = data;

  Logger.log(name + ' / ' + city + ' / ' + role);
  return { name: name, city: city, role: role, plan: plan, createdAt: createdAt };
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

The crash you will hit without defaults

UrlFetchApp.fetch returns an HTTPResponse object. You call getContentText() on it, hand that string to JSON.parse, and get a plain JavaScript object back. Destructuring that top-level object is fine. The problem is one level down.

If the API response omits the address field entirely, data.address is undefined. When V8 tries to evaluate { city, country } = undefined, it throws TypeError: Cannot destructure property 'city' of undefined. The run dies, the Sheet row goes unwritten, and the error is reported against the destructuring line, not the fetch, which makes it confusing the first time you hit it.

The fix is a default value in the destructuring pattern itself: { address: { city, country } = {} } = data. Now if address is absent, the right-hand side of that sub-pattern falls back to {}, city and country both land as undefined, and the rest of the function runs.

Reading multiple sibling keys in one declaration

You can destructure several nested paths in a single var statement by separating them with commas inside the outer braces. The snippet above does this for profile and meta in one line. This replaces three or four sequential assignments like var role = data.profile && data.profile.role — which is the pattern most Apps Script tutorials still teach because they predate V8.

Each nested object gets its own = {} default independently. If profile exists but meta does not, only meta falls back; profile is destructured normally. The defaults are per-path, not global.

One thing worth knowing: V8 in Apps Script does support destructuring, but the older Rhino runtime does not. If your script still targets Rhino (check Project Settings; the runtime label will say Rhino), none of this syntax is available. Rhino scripts need the old property-chain approach.

Handling arrays inside the parsed object

JSON from real APIs often nests arrays, not just objects. If the response has a tags field that is an array, { tags: [firstTag] = [] } = data gives you the first element as firstTag, and an empty array as the default if tags is absent. The = [] default prevents the same crash pattern on array destructuring.

For long arrays where you want all elements, skip destructuring and just read data.tags directly after the object destructure. Mixing array and object destructuring in one pattern gets hard to read past two or three keys. I keep the array reads as separate assignments below the main destructure block, which also makes it easier to add null checks on the array length before accessing indices.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Why does destructuring crash on a missing nested key but not a missing top-level key?
Top-level missing keys yield undefined, which is a valid value to assign. Nested destructuring first evaluates the parent property to get the object it will destructure next. If that parent is undefined, JavaScript cannot read properties off it at all, so it throws TypeError before your variable ever gets assigned.
Does muteHttpExceptions affect whether JSON.parse throws?
No. muteHttpExceptions: true tells UrlFetchApp to return a response object for 4xx/5xx status codes instead of throwing its own exception. JSON.parse can still throw SyntaxError independently if the response body is not valid JSON, for example if the server returns an HTML error page. Wrap the parse in a try/catch if the endpoint is unreliable.
Can I rename a destructured key at the same time as I set a default?
Yes. The syntax is { address: { city: userCity, country: userCountry } = {} } = data. The rename (city: userCity) and the default (= {}) compose. The colon means 'read this key, bind it to this new name'; the equals sign on the nested pattern means 'use this if the parent is absent'.
Does this work in Google Apps Script's V8 runtime for Sheets add-ons?
Yes, as long as the script is on V8. Destructuring is standard ES6 and V8 supports it fully. The older Rhino runtime predates ES6 and will throw a syntax error on any destructuring pattern. Check Edit > Project Settings > Runtime version if you are unsure which runtime a script is using.