Google Apps Script: Преобразование объекта в строку

Что такое Google Apps Script и где он применяется

Google Apps Script (GAS) — это облачная платформа для разработки скриптов, основанная на JavaScript, которая позволяет автоматизировать задачи и расширять функциональность приложений Google Workspace (Sheets, Docs, Forms, Drive, Gmail и др.). GAS выполняется на серверах Google, обеспечивая интеграцию между различными сервисами Google и внешними API.

Платформа используется для создания пользовательских меню, диалоговых окон, автоматизации документооборота, обработки данных в таблицах, создания веб-приложений и многого другого.

Зачем преобразовывать объекты в строки?

Преобразование объектов JavaScript в строки — частая задача в разработке скриптов. Основные причины:

Логирование: Запись состояния объекта или данных в логи (например, Stackdriver Logging / Google Cloud Logging) для отладки или мониторинга.

Хранение данных: Сохранение структурированных данных в текстовом формате, например, в ячейках Google Sheets, в текстовых файлах на Google Drive или в PropertiesService.

Передача данных: Отправка данных через HTTP-запросы (API), вебхуки или передача данных между различными функциями или скриптами, где требуется строковое представление.

Сериализация: Подготовка данных для сохранения или передачи в формате, который легко восстановить обратно в объект (чаще всего JSON).

Обзор типов объектов, с которыми работает Apps Script

В Google Apps Script вы работаете как со стандартными объектами JavaScript (массивы, даты, пользовательские объекты), так и со специфичными объектами, предоставляемыми сервисами Google Workspace:

Стандартные объекты JavaScript: { key: 'value' }, ['item1', 'item2'], new Date().

Объекты сервисов GAS: SpreadsheetApp.getActiveSpreadsheet(), DocumentApp.getActiveDocument(), GmailApp.getInboxThreads(), CalendarApp.getEventsForDay(new Date()).

Объекты событий: Объекты, передаваемые в триггеры (например, e в функции onEdit(e)).

Понимание типа объекта важно для выбора корректного метода преобразования в строку.

Использование JSON.stringify() для преобразования объектов

Основным и наиболее распространенным методом преобразования объектов в строку в JavaScript и, следовательно, в Google Apps Script является JSON.stringify().

Синтаксис и параметры JSON.stringify()

Метод JSON.stringify() преобразует значение JavaScript в строку JSON.

/**
 * Преобразует объект JavaScript в строку JSON.
 *
 * @param {any} value Значение для преобразования.
 * @param {(key: string, value: any) => any | (string | number)[]} [replacer] Функция-заменитель или массив строк/чисел для фильтрации свойств.
 * @param {string | number} [space] Строка или число для добавления отступов (форматирования) в вывод.
 * @return {string} Строка JSON.
 */
JSON.stringify(value[, replacer[, space]])

value: Преобразуемый объект.

replacer (необязательный): Функция для изменения процесса сериализации или массив строк/чисел, указывающий, какие свойства включать.

space (необязательный): Управляет отступами в выходной строке для удобочитаемости. Может быть числом (количество пробелов) или строкой (используется как отступ).

Примеры преобразования простых объектов в JSON-строку

/**
 * Пример преобразования простого объекта конфигурации в JSON.
 */
function simpleObjectToString() {
  const config = {
    apiKey: 'XYZ123',
    isEnabled: true,
    retryCount: 3
  };

  // Преобразование в компактную JSON-строку
  const jsonString = JSON.stringify(config);
  Logger.log(jsonString); // Вывод: "{"apiKey":"XYZ123","isEnabled":true,"retryCount":3}"

  // Преобразование в форматированную JSON-строку с отступом в 2 пробела
  const formattedJsonString = JSON.stringify(config, null, 2);
  Logger.log(formattedJsonString);
  /* Вывод:
  {
    "apiKey": "XYZ123",
    "isEnabled": true,
    "retryCount": 3
  }
  */
}

Преобразование сложных объектов, включая массивы и вложенные объекты

JSON.stringify() легко справляется с массивами и вложенными структурами.

/**
 * Пример преобразования сложного объекта с массивами и вложенностью.
 */
function complexObjectToString() {
  const campaignData = {
    id: 'CMP-001',
    name: 'Summer Sale Campaign',
    startDate: new Date(2023, 5, 1), // Июнь 1, 2023
    targets: [
      { region: ' EMEA', budget: 5000 },
      { region: 'NA', budget: 10000 }
    ],
    metadata: {
      owner: 'marketing@example.com',
      lastUpdated: new Date()
    }
  };

  // Используем replacer для форматирования дат в ISO строку (хотя JSON.stringify делает это по умолчанию)
  const replacer = (key, value) => {
    if (value instanceof Date) {
      return value.toISOString();
    }
    return value;
  };

  const jsonString = JSON.stringify(campaignData, replacer, 2);
  Logger.log(jsonString);
  /* Примерный вывод:
  {
    "id": "CMP-001",
    "name": "Summer Sale Campaign",
    "startDate": "2023-06-01T00:00:00.000Z", // Зависит от часового пояса сервера GAS
    "targets": [
      {
        "region": " EMEA",
        "budget": 5000
      },
      {
        "region": "NA",
        "budget": 10000
      }
    ],
    "metadata": {
      "owner": "marketing@example.com",
      "lastUpdated": "2023-10-27T10:30:00.000Z" // Текущая дата/время в ISO
    }
  }
  */
}

Обработка особых случаев: циклические ссылки и функции

JSON.stringify() имеет ограничения:

Функции и undefined: Свойства со значениями-функциями или undefined пропускаются при сериализации. Если такие значения встречаются в массиве, они заменяются на null.

Циклические ссылки: Если объект содержит ссылки сам на себя, JSON.stringify() выбросит TypeError. Чтобы обойти это, можно использовать функцию replacer для обнаружения и обработки таких ссылок.

/**
 * Демонстрация обработки функций и циклических ссылок.
 */
function handleSpecialCases() {
  const obj = {
    name: 'Test Object',
    data: { value: 1 },
    run: function() { console.log('Running...'); },
    nothing: undefined
  };
  obj.self = obj; // Создаем циклическую ссылку

  // Попытка прямой сериализации вызовет ошибку
  try {
    JSON.stringify(obj);
  } catch (e) {
    Logger.log(`Ошибка при прямой сериализации: ${e}`); // TypeError: Converting circular structure to JSON
  }

  // Использование replacer для удаления циклических ссылок и функций
  const getCircularReplacer = () => {
    const seen = new WeakSet();
    return (key, value) => {
      if (typeof value === 'object' && value !== null) {
        if (seen.has(value)) {
          return '[Circular]'; // Заменяем циклическую ссылку
        }
        seen.add(value);
      }
      if (typeof value === 'function') {
        return '[Function]'; // Заменяем функцию
      }
      if (typeof value === 'undefined') {
        return '[Undefined]'; // Явно указываем undefined
      }
      return value;
    };
  };

  const safeJsonString = JSON.stringify(obj, getCircularReplacer(), 2);
  Logger.log(safeJsonString);
  /* Вывод:
  {
    "name": "Test Object",
    "data": {
      "value": 1
    },
    "run": "[Function]",
    "nothing": "[Undefined]",
    "self": "[Circular]"
  }
  */
}

Другие методы преобразования объектов в строки

Хотя JSON.stringify() является стандартом, существуют и другие подходы.

Использование template literals (шаблонные литералы)

Шаблонные литералы (строки с обратными кавычками `) удобны для создания строк с динамическим содержимым, но не подходят для полной сериализации сложных объектов. Они полезны для форматированного вывода или создания простых строковых представлений.

/**
 * Использование шаблонных литералов для простого строкового представления.
 */
function templateLiteralExample() {
  const userData = {
    name: 'Alice',
    email: 'alice@example.com',
    lastLogin: new Date()
  };

  const userString = `User: ${userData.name}, Email: ${userData.email}, Last Login: ${userData.lastLogin.toLocaleString()}`;
  Logger.log(userString);
  // Вывод: User: Alice, Email: alice@example.com, Last Login: 10/27/2023, 11:00:00 AM (формат зависит от локали)
}

Ручное создание строки с помощью конкатенации

Этот метод наименее предпочтителен из-за сложности, подверженности ошибкам и плохой читаемости кода, особенно для сложных объектов. Используется крайне редко.

/**
 * Пример ручной конкатенации (не рекомендуется).
 */
function manualConcatenation() {
  const item = { id: 101, status: 'active' };
  const itemString = 'ID: ' + item.id + '; Status: ' + item.status;
  Logger.log(itemString); // Вывод: ID: 101; Status: active
}

Применение Utilities.jsonStringify (если требуется совместимость со старым кодом)

В Google Apps Script существует также метод Utilities.jsonStringify(). Он функционально идентичен JSON.stringify(). Его использование может быть оправдано в старых проектах для единообразия или если требуется явное указание на использование утилиты GAS, но в новом коде предпочтительнее стандартный JSON.stringify().

Реклама
/**
 * Пример использования Utilities.jsonStringify.
 */
function utilitiesJsonStringifyExample() {
  const data = { a: 1, b: [2, 3] };
  const jsonString = Utilities.jsonStringify(data);
  Logger.log(jsonString); // Вывод: "{"a":1,"b":[2,3]}"
}

Особенности преобразования объектов Google Apps Script

Преобразование объектов, возвращаемых API Google Workspace (например, Sheets, Docs)

Объекты, возвращаемые сервисами GAS (например, Spreadsheet, Range, Document, Folder), часто являются сложными Java-объектами на стороне сервера с множеством методов, но ограниченным количеством сериализуемых свойств. Прямое применение JSON.stringify() к таким объектам обычно не дает полезного результата или возвращает пустой объект {}.

Вместо этого необходимо извлечь нужные данные с помощью методов этих объектов и поместить их в простой JavaScript-объект перед сериализацией.

/**
 * Пример извлечения данных из объекта Range перед сериализацией.
 */
function serializeSheetData() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getActiveSheet();
  const range = sheet.getRange('A1:B2');

  // Прямая сериализация объекта Range бесполезна
  // Logger.log(JSON.stringify(range)); // Выведет {} или что-то неинформативное

  // Извлекаем нужные данные
  const data = {
    values: range.getValues(),
    formulas: range.getFormulas(),
    sheetName: sheet.getName(),
    rangeA1Notation: range.getA1Notation()
  };

  const jsonString = JSON.stringify(data, null, 2);
  Logger.log(jsonString);
  /* Примерный вывод:
  {
    "values": [
      ["Header 1", "Header 2"],
      ["Data A", "Data B"]
    ],
    "formulas": [
      ["", ""],
      ["", ""]
    ],
    "sheetName": "Sheet1",
    "rangeA1Notation": "A1:B2"
  }
  */
}

Работа с датами и другими специфическими типами данных

Как упоминалось, JSON.stringify() автоматически преобразует объекты Date в строки формата ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ). При обратном преобразовании строки JSON в объект с помощью JSON.parse(), строки дат не преобразуются обратно в объекты Date автоматически. Это нужно делать вручную, например, с помощью параметра reviver в JSON.parse().

Другие специфические типы, такие как Map или Set, не сериализуются стандартным JSON.stringify() корректно. Их нужно предварительно преобразовать в массивы или объекты.

Обработка ошибок и исключений при преобразовании

Основная ошибка при использовании JSON.stringify() — это TypeError при наличии циклических ссылок. Важно использовать блоки try...catch при работе с потенциально сложными или неизвестными структурами данных, чтобы предотвратить аварийное завершение скрипта.

/**
 * Обработка потенциальной ошибки при сериализации.
 *
 * @param {any} dataToSerialize Данные для сериализации.
 * @return {string | null} Строка JSON или null в случае ошибки.
 */
function safeStringify(dataToSerialize) {
  try {
    // Используем replacer для предотвращения циклических ссылок
    const getCircularReplacer = () => {
      const seen = new WeakSet();
      return (key, value) => {
        if (typeof value === 'object' && value !== null) {
          if (seen.has(value)) {
            return '[Circular Reference]';
          }
          seen.add(value);
        }
        return value;
      };
    };
    return JSON.stringify(dataToSerialize, getCircularReplacer());
  } catch (error) {
    Logger.log(`Ошибка сериализации: ${error.message}`);
    // Можно добавить более детальное логирование или обработку
    console.error('Не удалось сериализовать объект:', error);
    return null; // Возвращаем null или другое индикативное значение
  }
}

Примеры использования преобразованных строк

Преобразование объектов в строки находит применение во многих сценариях автоматизации.

Логирование данных в Google Cloud Logging

GAS интегрирован с Google Cloud Logging (ранее Stackdriver Logging). Использование console.log(), console.info(), console.warn(), console.error() позволяет отправлять структурированные логи.

/**
 * Логирование структурированных данных в Cloud Logging.
 */
function logToCloud() {
  const eventData = {
    timestamp: new Date().toISOString(),
    source: 'DataProcessingTrigger',
    payload: { userId: 123, action: 'update', success: true },
    executionTimeMs: 150
  };

  // Cloud Logging автоматически обработает объект, но явное использование
  // console.log с объектом или строкой JSON гарантирует читаемость.
  console.log({ message: 'Processing event', event: eventData });

  // Можно также сначала преобразовать в строку, если нужно логгировать именно JSON
  const eventString = JSON.stringify(eventData);
  console.log(`Raw event JSON: ${eventString}`);
}

Сохранение данных в Google Sheets или Google Docs

Строки JSON можно легко сохранять в ячейках Google Sheets или в тексте Google Docs для последующего извлечения и парсинга.

/**
 * Сохранение конфигурации в ячейку Google Sheets.
 */
function saveConfigToSheet() {
  const config = {
    sheetId: 'abc123xyz',
    syncEnabled: true,
    lastSync: new Date().toISOString()
  };
  const configString = JSON.stringify(config);

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const configSheet = ss.getSheetByName('Configuration') || ss.insertSheet('Configuration');
  configSheet.getRange('A1').setValue(configString);
  Logger.log('Конфигурация сохранена в ячейку A1.');
}

/**
 * Загрузка конфигурации из Google Sheets.
 *
 * @return {object | null} Разобранный объект конфигурации или null.
 */
function loadConfigFromSheet() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const configSheet = ss.getSheetByName('Configuration');
  if (!configSheet) return null;

  const configString = configSheet.getRange('A1').getValue();
  if (!configString) return null;

  try {
    const config = JSON.parse(configString);
    // Опционально: преобразовать строки дат обратно в объекты Date
    if (config.lastSync) {
      config.lastSync = new Date(config.lastSync);
    }
    return config;
  } catch (e) {
    Logger.log(`Ошибка парсинга конфигурации: ${e}`);
    return null;
  }
}

Отправка данных через API

При взаимодействии с внешними API часто требуется отправлять данные в формате JSON в теле POST-запроса.

/**
 * Отправка данных о событии на внешний API.
 */
function sendDataToApi() {
  const eventPayload = {
    event: 'user_signup',
    user_id: 'usr_789',
    timestamp: new Date().toISOString(),
    details: { source: 'webapp', referrer: 'google' }
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(eventPayload), // Сериализуем объект в JSON
    muteHttpExceptions: true // Чтобы обработать ошибки вручную
  };

  const url = 'https://api.example.com/events';
  const response = UrlFetchApp.fetch(url, options);

  Logger.log(`API Response Code: ${response.getResponseCode()}`);
  Logger.log(`API Response Body: ${response.getContentText()}`);
}

Передача данных между функциями и скриптами

При использовании PropertiesService для хранения данных между выполнениями скрипта или для обмена данными между разными скриптами (если они имеют доступ к одному и тому же хранилищу свойств), объекты необходимо сериализовать в строки.

/**
 * Сохранение пользовательских настроек в PropertiesService.
 */
function saveUserSettings() {
  const userSettings = {
    theme: 'dark',
    notifications: { email: true, push: false },
    language: 'ru'
  };

  const settingsString = JSON.stringify(userSettings);
  PropertiesService.getUserProperties().setProperty('userSettings', settingsString);
  Logger.log('Настройки пользователя сохранены.');
}

/**
 * Загрузка пользовательских настроек из PropertiesService.
 *
 * @return {object} Объект настроек пользователя.
 */
function loadUserSettings() {
  const settingsString = PropertiesService.getUserProperties().getProperty('userSettings');
  if (settingsString) {
    try {
      return JSON.parse(settingsString);
    } catch (e) {
      Logger.log(`Ошибка парсинга настроек: ${e}`);
      // Возвращаем настройки по умолчанию
      return { theme: 'light', notifications: { email: true, push: true }, language: 'en' };
    }
  } else {
    // Настройки не найдены, возвращаем по умолчанию
    return { theme: 'light', notifications: { email: true, push: true }, language: 'en' };
  }
}

Освоение методов преобразования объектов в строки, особенно JSON.stringify(), является ключевым навыком для эффективной разработки на Google Apps Script, позволяя гибко управлять данными при логировании, хранении и передаче.


Добавить комментарий