Документация Руководство по JsonPath

JsonPath руководство для Mockarty

Содержание

  1. Что такое JsonPath
  2. Основной синтаксис
  3. Примеры использования
  4. Извлечение данных
  5. Переменные в ответах
  6. Store доступ
  7. Faker интеграция
  8. Сложные примеры

Совет: Примеры кода доступны для cURL, CLI и SDK-клиентов (Go, Python, Java). Смотрите Руководство по SDK для установки и настройки. Смотрите Руководство по CLI для командной утилиты.

URL-адреса в примерах: Во всех примерах используется localhost:5770 как адрес Mockarty по умолчанию. Если ваш экземпляр работает на удалённом сервере, замените localhost:5770 на реальный адрес (например, https://mockarty.company.com или http://192.168.1.50:5770). Подробнее — в разделе Полезные функции и советы.

Что такое JsonPath

JsonPath – это язык запросов для JSON-документов, аналогичный XPath для XML. Представьте его как систему “адресов” для данных внутри JSON – точно так же, как почтовый адрес помогает найти дом в городе, JsonPath-выражение помогает найти конкретное значение внутри JSON-документа.

Например, если у вас есть JSON-объект {"user": {"name": "John"}}, выражение $.user.name указывает прямо на "John". Символ $ означает “начать с корня документа”, а точки позволяют перемещаться вглубь вложенных объектов.

Когда это нужно? Всякий раз, когда вы хотите, чтобы мок вёл себя динамически, а не возвращал статичный ответ. Например, если клиент отправляет запрос с ID пользователя, вы можете с помощью JsonPath извлечь этот ID и включить его в ответ – и мок будет вести себя как настоящий сервис.

В Mockarty JsonPath используется для:

  • Извлечения данных из входящих запросов
  • Доступа к Store данным (Global, Chain, Mock)
  • Генерации динамических ответов с переменными через Faker
  • Проверки условий при мэтчинге моков

Библиотека

Mockarty использует библиотеку github.com/PaesslerAG/jsonpath с поддержкой расширенного синтаксиса.


Основной синтаксис

Базовые выражения

Выражение Описание Пример
$ Корень документа $.user
. Текущий узел $.user.name
[] Доступ по индексу/ключу $.items[0], $.user['name']
* Все элементы $.users[*].name
.. Рекурсивный поиск $..email
?() Фильтр $.items[?(@.price > 100)]

Совет: Точка после $ необязательна – $req.field автоматически нормализуется в $.req.field.

Специальные префиксы Mockarty

Префикс Описание Пример
$.req Тело запроса $.req.userId
$.reqHeader Заголовки запроса $.reqHeader.Authorization[0]
$.queryParams Query параметры HTTP $.queryParams.limit
$.mS Mock Store $.mS.savedValue
$.cS Chain Store $.cS.sessionId
$.gS Global Store $.gS.counter
$.pathParam Параметры URL-пути $.pathParam.id для маршрута /api/users/:id
$.fake Faker данные $.fake.FirstName
$.response Данные ответа (для webhook) $.response.orderId
$.namespace Текущий namespace $.namespace

Математические функции

Функция Описание Пример
$.increment(expr || default) Увеличить значение на 1 $.increment($.gS.counter || 0)
$.sum(expr1, expr2) Сумма двух значений $.sum($.req.price, $.req.tax)
$.multiply(expr1, expr2) Умножение двух значений $.multiply($.req.quantity, $.req.price)
$.subtract(expr1, expr2) Вычитание (expr1 - expr2) $.subtract($.req.total, $.req.discount)

Аргументами могут быть:

  • Числовые литералы: $.sum(10, 20)
  • JsonPath выражения: $.sum($.req.a, $.req.b)
  • Store ссылки: $.sum($.gS.total, $.req.amount)
  • Выражения с default: $.increment($.gS.counter || 0) – если значение отсутствует, используется 0

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

Простое извлечение

JSON документ:

{
  "user": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com"
  }
}

JsonPath выражения:

$.user.id        // → 123
$.user.name      // → "John Doe"  
$.user.email     // → "john@example.com"
$['user']['id']  // → 123 (альтернативный синтаксис)

Работа с массивами

JSON документ:

{
  "users": [
    {"id": 1, "name": "John", "active": true},
    {"id": 2, "name": "Jane", "active": false},
    {"id": 3, "name": "Bob", "active": true}
  ]
}

JsonPath выражения:

$.users[0]             // → {"id": 1, "name": "John", "active": true}
$.users[0].name        // → "John"
$.users[-1]            // → {"id": 3, "name": "Bob", "active": true} (последний)
$.users[*].name        // → ["John", "Jane", "Bob"]
$.users[*].id          // → [1, 2, 3]

Фильтрация

// Активные пользователи
$.users[?(@.active == true)]
// → [{"id": 1, "name": "John", "active": true}, {"id": 3, "name": "Bob", "active": true}]

// Имена активных пользователей
$.users[?(@.active == true)].name
// → ["John", "Bob"]

// Пользователи с ID больше 1
$.users[?(@.id > 1)]

// Пользователи с именем содержащим "J"
$.users[?(@.name =~ /.*J.*/i)]

Рекурсивный поиск

JSON документ:

{
  "company": {
    "departments": [
      {
        "name": "IT",
        "employees": [
          {"name": "John", "email": "john@company.com"},
          {"name": "Jane", "email": "jane@company.com"}
        ]
      },
      {
        "name": "HR", 
        "head": {"name": "Bob", "email": "bob@company.com"}
      }
    ]
  }
}

JsonPath выражения:

$..email    // → ["john@company.com", "jane@company.com", "bob@company.com"]
$..name     // → ["IT", "John", "Jane", "HR", "Bob"]

Извлечение данных

Извлечение из запроса

HTTP запрос:

POST /api/users
{
  "user": {
    "name": "John Doe",
    "email": "john@example.com",
    "preferences": {
      "theme": "dark",
      "notifications": true
    }
  }
}

Извлечение в Mock:

{
  "extract": {
    "mStore": {
      "userName": "$.req.user.name",
      "userEmail": "$.req.user.email",
      "userTheme": "$.req.user.preferences.theme"
    },
    "gStore": {
      "lastUserName": "$.req.user.name"
    }
  }
}

Извлечение заголовков

HTTP запрос:

POST /api/data
Authorization: Bearer abc123token
Content-Type: application/json
X-User-ID: user-456

Извлечение:

{
  "extract": {
    "mStore": {
      "authToken": "$.reqHeader.Authorization[0]",
      "userId": "$.reqHeader['X-User-ID'][0]",
      "contentType": "$.reqHeader['Content-Type'][0]"
    }
  }
}

Важно: Заголовки – это массивы (один заголовок может иметь несколько значений). Используйте [0] для получения первого значения.

Извлечение query параметров

Для query строки (?limit=10&offset=20) используйте $.queryParams:

Запрос: GET /api/users?limit=10&offset=20&sort=name

{
  "response": {
    "payload": {
      "limit": "$.queryParams.limit",
      "offset": "$.queryParams.offset",
      "sort": "$.queryParams.sort"
    }
  }
}

Можно использовать query параметры в условиях:

{
  "conditions": [
    {
      "queryParam": "status",
      "assertAction": "equals",
      "value": "active"
    }
  ]
}

Сложные условия

{
  "conditions": [
    {
      "path": "$.order.items[*].price",
      "assertAction": "contains",
      "value": 100
    },
    {
      "path": "$.order.items[?(@.category == 'electronics')].price",
      "assertAction": "matches",
      "value": "^[1-9]\\d{2,}$"
    }
  ]
}

Переменные в ответах

Использование извлеченных данных

{
  "response": {
    "statusCode": 201,
    "payload": {
      "id": "$.fake.UUIDDigit",
      "name": "$.req.user.name",
      "email": "$.req.user.email",
      "welcomeMessage": "Hello $.req.user.name!",
      "timestamp": "$.fake.RFC3339",
      "preferences": "$.req.user.preferences"
    }
  }
}

Объединение значений

{
  "response": {
    "payload": {
      "fullName": "$.req.firstName $.req.lastName",
      "displayName": "Mr. $.req.lastName",
      "loginUrl": "https://example.com/login?user=$.req.userId"
    }
  }
}

Store доступ

Полное руководство по системам хранилищ см. в документации Store Systems.

Global Store

Сохранение в Global Store:

{
  "extract": {
    "gStore": {
      "totalUsers": "$.increment($.gS.totalUsers || 0)",
      "lastActiveUser": "$.req.userId",
      "systemStats": {
        "lastRequest": "$.fake.RFC3339",
        "version": "1.0.0"
      }
    }
  }
}

Использование Global Store:

{
  "response": {
    "payload": {
      "userId": "$.req.id",
      "totalUsersInSystem": "$.gS.totalUsers",
      "lastActiveUser": "$.gS.lastActiveUser",
      "systemVersion": "$.gS.systemStats.version"
    }
  }
}

Chain Store

Первый мок в цепочке:

{
  "chainId": "user-registration",
  "extract": {
    "cStore": {
      "userId": "$.fake.UUIDDigit",
      "userEmail": "$.req.email",
      "registrationTime": "$.fake.RFC3339"
    }
  },
  "response": {
    "payload": {
      "userId": "$.cS.userId",
      "status": "registered"
    }
  }
}

Второй мок в цепочке:

{
  "chainId": "user-registration",
  "response": {
    "payload": {
      "userId": "$.cS.userId",
      "email": "$.cS.userEmail",
      "registeredAt": "$.cS.registrationTime",
      "status": "verified"
    }
  }
}

Faker интеграция

Полный список всех доступных Faker-функций см. в Справочнике Faker.

Основные Faker функции

{
  "response": {
    "payload": {
      "id": "$.fake.UUID",
      "name": "$.fake.FirstName",
      "lastName": "$.fake.LastName",
      "email": "$.fake.Email",
      "phone": "$.fake.Phone",
      "address": "$.fake.Address",
      "city": "$.fake.City",
      "country": "$.fake.Country",
      "company": "$.fake.Company",
      "jobTitle": "$.fake.JobTitle",
      "website": "$.fake.URL",
      "avatar": "$.fake.URL",
      "birthDate": "$.fake.Date",
      "timestamp": "$.fake.RFC3339",
      "unixTime": "$.fake.UnixTime"
    }
  }
}

Числовые Faker функции

{
  "response": {
    "payload": {
      "randomInt": "$.fake.Int",
      "positiveInt": "$.fake.PositiveInt",
      "negativeInt": "$.fake.NegativeInt",
      "float": "$.fake.Float",
      "price": "$.fake.Price",
      "latitude": "$.fake.Latitude",
      "longitude": "$.fake.Longitude",
      "boolean": "$.fake.Bool"
    }
  }
}

Локализованные данные

{
  "response": {
    "payload": {
      "russianName": "$.fake.RussianFirstNameMale",
      "russianSurname": "$.fake.RussianLastNameMale",
      "chineseName": "$.fake.FirstName",
      "germanName": "$.fake.FirstName",
      "sentence": "$.fake.Sentence",
      "paragraph": "$.fake.Paragraph",
      "word": "$.fake.Word"
    }
  }
}

Математические операции

Подробные примеры

Счетчик с инкрементом

{
  "extract": {
    "gStore": {
      "visitCount": "$.increment($.gS.visitCount || 0)"
    }
  },
  "response": {
    "payload": {
      "visits": "$.gS.visitCount"
    }
  }
}

При каждом вызове visitCount увеличивается на 1. Конструкция || 0 задает начальное значение, если ключ отсутствует.

Суммирование и умножение

{
  "response": {
    "payload": {
      "subtotal": "$.req.price",
      "tax": "$.multiply($.req.price, 0.2)",
      "total": "$.sum($.req.price, $.multiply($.req.price, 0.2))",
      "discount": "$.subtract($.req.price, 10)"
    }
  }
}

Важно: Математические функции работают с числовыми значениями. Строки автоматически преобразуются в числа. Если преобразование невозможно, используется значение 0.

Строковая интерполяция

JsonPath выражения можно встраивать прямо в текстовые строки:

{
  "response": {
    "payload": {
      "greeting": "Hello $.req.name, welcome!",
      "link": "https://example.com/users/$.req.id/profile",
      "message": "Order #$.fake.UUIDDigit confirmed for $.req.email"
    }
  }
}

Все выражения $.xxx внутри строк будут автоматически заменены на соответствующие значения.


Сложные примеры

Динамический счетчик

{
  "id": "counter-example",
  "http": {
    "route": "/api/counter"
  },
  "response": {
    "payload": {
      "currentCount": "$.gS.counter",
      "incrementedCount": "$.increment($.gS.counter || 0)",
      "timestamp": "$.fake.RFC3339"
    }
  },
  "extract": {
    "gStore": {
      "counter": "$.increment($.gS.counter || 0)",
      "lastIncrement": "$.fake.RFC3339"
    }
  }
}

User Management Chain

Регистрация (Шаг 1):

{
  "id": "user-register",
  "chainId": "user-lifecycle",
  "http": {
    "route": "/api/v1/auth/register",
    "httpMethod": "POST"
  },
  "response": {
    "payload": {
      "userId": "$.fake.UUIDDigit",
      "email": "$.req.email",
      "status": "pending_verification",
      "verificationCode": "$.fake.ZipCode"
    }
  },
  "extract": {
    "cStore": {
      "userId": "$.fake.UUIDDigit",
      "userEmail": "$.req.email",
      "verificationCode": "$.fake.ZipCode",
      "registeredAt": "$.fake.RFC3339"
    },
    "gStore": {
      "totalRegistrations": "$.increment($.gS.totalRegistrations || 0)"
    }
  }
}

Верификация (Шаг 2):

{
  "id": "user-verify",
  "chainId": "user-lifecycle",
  "http": {
    "route": "/api/v1/auth/verify",
    "httpMethod": "POST",
    "conditions": [
      {
        "assertAction": "equals",
        "value": {
          "code": "$.cS.verificationCode"
        }
      }
    ]
  },
  "response": {
    "payload": {
      "userId": "$.cS.userId",
      "email": "$.cS.userEmail",
      "status": "verified",
      "verifiedAt": "$.fake.RFC3339",
      "accessToken": "$.fake.JWT"
    }
  },
  "extract": {
    "cStore": {
      "isVerified": true,
      "verifiedAt": "$.fake.RFC3339",
      "accessToken": "$.fake.JWT"
    }
  }
}

Профиль (Шаг 3):

{
  "id": "user-profile",
  "chainId": "user-lifecycle",
  "http": {
    "route": "/api/users/profile",
    "httpMethod": "GET",
    "header": [
      {
        "assertAction": "contains",
        "value": {
          "Authorization": "Bearer $.cS.accessToken"
        }
      }
    ]
  },
  "response": {
    "payload": {
      "userId": "$.cS.userId",
      "email": "$.cS.userEmail",
      "verified": "$.cS.isVerified",
      "registeredAt": "$.cS.registeredAt",
      "verifiedAt": "$.cS.verifiedAt",
      "profile": {
        "name": "$.fake.FirstName",
        "lastName": "$.fake.LastName",
        "avatar": "$.fake.URL",
        "bio": "$.fake.Sentence",
        "preferences": {
          "theme": "dark",
          "language": "en",
          "notifications": true
        }
      },
      "stats": {
        "loginCount": "$.fake.Number",
        "lastLogin": "$.fake.RFC3339",
        "totalUsers": "$.gS.totalRegistrations"
      }
    }
  }
}

E-commerce Order Processing

{
  "id": "process-order",
  "http": {
    "route": "/api/orders",
    "httpMethod": "POST",
    "conditions": [
      {
        "path": "$.items[*].quantity",
        "assertAction": "matches",
        "value": "^[1-9]\\d*$"
      },
      {
        "path": "$.customer.email",
        "assertAction": "matches",
        "value": "^[\\w\\.-]+@[\\w\\.-]+\\.[a-zA-Z]{2,}$"
      }
    ]
  },
  "response": {
    "payload": {
      "orderId": "$.fake.UUIDDigit",
      "customer": "$.req.customer",
      "items": "$.req.items",
      "summary": {
        "estimatedTotal": "$.fake.Float",
        "currency": "USD",
        "orderedItems": "$.req.items"
      },
      "fulfillment": {
        "estimatedDelivery": "$.fake.Date",
        "trackingNumber": "$.fake.ZipCode",
        "carrier": "$.fake.Company"
      },
      "timestamps": {
        "orderedAt": "$.fake.RFC3339",
        "estimatedShipping": "$.fake.Date",
        "estimatedDelivery": "$.fake.Date"
      }
    }
  },
  "extract": {
    "mStore": {
      "orderId": "$.fake.UUIDDigit",
      "customerEmail": "$.req.customer.email"
    },
    "gStore": {
      "totalOrders": "$.increment($.gS.totalOrders || 0)",
      "lastOrderTime": "$.fake.RFC3339"
    }
  }
}

Analytics Mock

{
  "id": "analytics-report",
  "http": {
    "route": "/api/analytics/report",
    "queryParams": [
      {
        "path": "$.period",
        "assertAction": "matches",
        "value": "^(daily|weekly|monthly)$"
      }
    ]
  },
  "response": {
    "payload": {
      "report": {
        "period": "$.queryParams.period",
        "generatedAt": "$.fake.RFC3339",
        "metrics": {
          "totalUsers": "$.gS.totalRegistrations",
          "totalOrders": "$.gS.totalOrders",
          "activeUsers": "$.fake.Number",
          "revenue": "$.fake.Float",
          "conversionRate": "$.fake.Float"
        },
        "trends": {
          "userGrowth": "$.fake.Float",
          "orderGrowth": "$.fake.Float",
          "revenueGrowth": "$.fake.Float"
        },
        "topProducts": [
          {
            "id": "$.fake.UUIDDigit",
            "name": "$.fake.Word", 
            "sales": "$.fake.Number",
            "revenue": "$.fake.Float"
          }
        ]
      }
    }
  }
}

Утилиты для работы с JsonPath

Страница утилит – тестер JsonPath

Тестирование JsonPath

Mockarty предоставляет утилитарный endpoint для тестирования JsonPath-выражений на произвольном JSON без создания мока. Доступен через страницу Utils в админ-панели и как REST endpoint.

Endpoint: POST /api/v1/utils/extract-jpath

Тело запроса:

Поле Тип Описание
jpath string JsonPath-выражение для вычисления
from object JSON-документ, к которому применяется выражение

Пример cURL:

curl -X POST http://localhost:5770/api/v1/utils/extract-jpath \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $MOCKARTY_API_TOKEN" \
  -d '{
    "jpath": "$.users[*].email",
    "from": {
      "users": [
        {"name": "John", "email": "john@example.com"},
        {"name": "Jane", "email": "jane@example.com"}
      ]
    }
  }'

Ответ:

{
  "extract": ["john@example.com", "jane@example.com"]
}

Endpoint намеренно не покрыт SDK — это отладочный helper, а не часть автоматизационного API. SDK (Go/Python/Java) и CLI не содержат специальной обёртки; используйте обычный HTTP POST из вашего языка или страницу Utils → JsonPath Tester в админ-панели.

Отладка выражений

Пример отладочного мока:

{
  "id": "debug-jsonpath",
  "http": {
    "route": "/debug/jsonpath"
  },
  "response": {
    "payload": {
      "request": "$.req",
      "headers": "$.reqHeader",
      "mockStore": "$.mS",
      "chainStore": "$.cS",
      "globalStore": "$.gS",
      "fakeData": {
        "uuid": "$.fake.UUID",
        "name": "$.fake.FirstName",
        "email": "$.fake.Email"
      }
    }
  }
}

Известные ограничения

При использовании JsonPath-выражений внутри строковых значений JSON (не как самостоятельное значение поля) действуют следующие ограничения из-за regex-движка интерполяции:

Возможность Самостоятельное значение Внутри строки
Простые пути $.req.user.id Работает Работает
Индекс массива $.req.items[0] Работает Работает
Отрицательный индекс $.req.items[-1] Работает НЕ работает
Wildcard $.req.items[*].name Работает НЕ работает
Рекурсивный спуск $..email Работает НЕ работает
Фильтр $.items[?(@.price>100)] Работает НЕ работает

Fallback-синтаксис || работает только внутри аргументов математических функций:

  • Работает: $.increment($.gS.counter || 0)
  • НЕ работает как самостоятельное выражение: $.req.userId || "default"

Неразрешённые выражения возвращают строку ошибки вида "Error: JSONPath '$.req.missing' not found" как в JSON-, так и в XML-ответах. Это касается как самостоятельных значений $.xxx, так и выражений $.xxx, встроенных в строки. Используйте синтаксис || default внутри математических функций (например, $.increment($.gS.counter || 0)), чтобы корректно обрабатывать отсутствующие значения.

Глубина рекурсии ограничена 15 уровнями вложенности JSON-объектов.


Лучшие практики

  1. Используйте осмысленные имена в Store:

    "extract": {
      "cStore": {
        "userId": "$.req.id",
        "userEmail": "$.req.email",
        "sessionStartTime": "$.fake.RFC3339"
      }
    }
    
  2. Используйте || fallback в математических функциях для обработки отсутствующих значений:

    $.increment($.gS.counter || 0)
    $.sum($.gS.price || 0, $.req.tax)
    

    Примечание: Синтаксис || работает только внутри аргументов математических функций ($.increment, $.sum, $.multiply, $.subtract). Он НЕ работает как самостоятельное выражение вроде $.req.user.id || "default-id".

  3. Используйте фильтры для сложной логики:

    $.orders[?(@.status == 'completed' && @.amount > 100)]
    
  4. Комбинируйте статические и динамические данные:

    {
      "message": "Hello $.req.name! Your order #$.fake.UUIDDigit is confirmed",
      "timestamp": "$.fake.RFC3339"
    }
    

XML поддержка

JsonPath выражения также работают внутри XML ответов (SOAP моки):

{
  "response": {
    "payload": "<GetUserResponse><UserId>$.req.id</UserId><Name>$.fake.FirstName</Name><Email>$.fake.Email</Email></GetUserResponse>"
  }
}

Mockarty автоматически экранирует спецсимволы XML (<, >, &, ", ') при подстановке значений в XML строки. Также нормализует пробелы в namespace-ах и атрибутах.


Go Template режим

Помимо JsonPath, Mockarty поддерживает Go Template синтаксис для файловых шаблонов. Загрузите шаблон из файла через поле templateFile:

{
  "response": {
    "templateFile": "my-template.json"
  }
}

Доступные функции внутри Go Template:

Функция Описание Пример
req Данные запроса (без аргументов, возвращает объект запроса) {{(req).user.name}}
reqHeader Данные заголовка (без аргументов, возвращает map заголовков) {{index (reqHeader) "Authorization"}}
fake Faker данные (без аргументов, возвращает объект faker) {{(fake).FirstName}}
mS Mock Store (без аргументов, возвращает map хранилища) {{index (mS) "key"}}
cS Chain Store (без аргументов, возвращает map хранилища) {{index (cS) "key"}}
gS Global Store (без аргументов, возвращает map хранилища) {{index (gS) "key"}}
asJson Преобразование в JSON {{asJson .Request}}

Важно: Go Template и JsonPath – два разных режима. JsonPath работает в полях payload (JSON), Go Template работает в templateFile.


Используйте это руководство для создания мощных динамических моков с JsonPath!


Смотрите также