# Урок 5. Работа с JSON

Если вы прошли расширенные уроки, то можете смело переходить к разделу...

Работу с JSON будем рассматривать на примере карточки заказа, так как заказ имеет вложенные сущности и потенциально может иметь больше уровней вложенности. Теперь будет интересно увидеть, как изменится форма с использованием JSON-объекта.

## Построение объекта <a href="#building-object" id="building-object"></a>

С формы заказа на сервер должен уходить JSON-объект подобного вида:

```json
{
   "order_id":3,
   "client_id":7,
   "order_position":[
      {
         "order_position_id":1,
         "material_id":4,
         "quantity":25.00,
         "unit_price":200.00,
         "updated": true,
         "deleted": false
      },
      {
         "order_position_id":null,
         "material_id":2,
         "quantity":2.00,
         "unit_price":350.00,
         "updated": false,
         "deleted": false
      }
   ]
}
```

Так как JSON-объект представляет собой набор пар "ключ-значение", то нам необходимо создать структуру `<Structure>` типа Dictionary, которую присвоим в объект Variable:

{% code title="TemplateOrderEdit.xml" %}
```xml
<MyObject Name="OrderDictionaryVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>
    <Structure Type="Dictionary">
      <Key Name=""></Key>
    </Structure>
  </Value>
</MyObject>
```
{% endcode %}

Обратите внимание, что у тэга `<MyObject>` стоит атрибут `ChangeForm` со значением False - этот объект вспомогательный, форма не должна его учитывать при проверке наличия изменений.

Добавим необходимые ключи в структуру и укажем источники данных:

```xml
<MyObject Name="OrderDictionaryVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>
    <Structure Type="Dictionary">
      <Key Name="order_id">
        <DataTypeConvert Type="IntegerDataType">
          <Parameter Name="OrderId" Refresh="False" />
        </DataTypeConvert>
      </Key>
      <Key Name="client_id">
        <DataConnection SourceDataConnection="OrderPrimaryGetDataConnection" GetScalar="True" Refresh="False">
          <Fields>
            <Field Name="ClientId" />
          </Fields>
        </DataConnection>
      </Key>
      <Key Name="order_position">
        <Array>
          <Source>
            <DataConnection SourceDataConnection="OrderPositionConvertDataConnection" Refresh="False">
              <Fields>
                <Field Name="OrderPositionId" />
                <Field Name="MaterialId" />
                <Field Name="Quantity" />
                <Field Name="UnitPrice" />
                <Field Name="Updated" />
                <Field Name="Deleted" />
              </Fields>
            </DataConnection>
          </Source>
          <ToDictionary>
            <Key Name="order_position_id" Index="0" />
            <Key Name="material_id" Index="1" />
            <Key Name="quantity" Index="2" />
            <Key Name="unit_price" Index="3" />
            <Key Name="updated" Index="4" />
            <Key Name="deleted" Index="5" />
          </ToDictionary>
        </Array>
      </Key>
    </Structure>
  </Value>
</MyObject>
```

Для поля client\_id из OrderPrimaryGetDataConnection необходимо получить одно единственное значение - для этого используем атрибут `GetScalar` со значением True.

Для поля order\_position, содержащего массив объектов, используем конструкцию `<Array>` с преобразованием массива строк из ConvertDataConnection в массив словарей.

У тэгов `<Parameter>` и `<DataConnection>` появился атрибут `Refresh`, который определяет, будет ли обновляться значение у тэгов `<Key>` и `<Array>`, если изменится значение источника. Таким образом, значение объекта OrderDictionaryVariable не будет пересчитываться каждый раз, когда измениться какой-либо источник.

Для ручного пересчета OrderDictionaryVariable нужно использовать команду ValueSetCommand для вызова set-проперти `Refresh` у объекта Variable:

{% code title="TemplateOrderEdit.xml" %}
```xml
<Command Name="OrderDictionaryVariableRefreshValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="OrderDictionaryVariable">
    <Property Name="Refresh" />
  </Object>
</Command>
```
{% endcode %}

Чтобы словарь преобразовать к JSON-объекту, создадим команду типа SerializeToJsonCommand:

{% code title="TemplateOrderEdit.xml" %}
```xml
<Command Name="OrderDictionaryVariableSerializeToJsonCommand" Type="SerializeToJsonCommand" Assembly="Commands">
  <Variable>
    <Object Name="OrderDictionaryVariable" />
  </Variable>
</Command>
```
{% endcode %}

Команда SerializeToJsonCommand при формировании JSON-объекта преобразует все даты со временем к UTC относительно пользовательских настроек временной зоны.

## Сохранение изменений <a href="#saving-changes" id="saving-changes"></a>

Добавьте кнопку сохранить, как делали на экране со списком ТМЦ.

Создадим SetDataConnection, который в параметре Model будет передавать на сервер сериализованный JSON-объект:

```xml
<DataConnection Name="OrderUpdateSetDataConnection" Type="SetDataConnection" Assembly="DataConnections">
  <Workflow Name="Template" />
  <SqlQueries>
    <SqlQuery Name="AppOrderSaveSqlQuery" Type="Update" />
  </SqlQueries>
  <Parameters>
    <Parameter NativeName="Model">
      <Value>
        <Command Name="OrderDictionaryVariableSerializeToJsonCommand" />
      </Value>
    </Parameter>
  </Parameters>
</DataConnection>
```

Создадим последовательность команд SaveSequentialCommand, в которую добавим команды на построение JSON\_объекта и команду передачи данных на сервер:

{% code title="TemplateOrderEdit.xml" %}
```xml
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <Command Name="OrderDictionaryVariableRefreshValueSetCommand" />
    <Command Name="OrderDictionaryVariableSerializeToJsonCommand" />

    <Command Name="OrderUpdateSaveCommand" />
    <Command Name="UpdatedTrueValueSetCommand" />
  </Commands>
</Command>
```
{% endcode %}

Перейдем в серверный xml-файл и создадим запрос AppOrderSaveSqlQuery:

{% code title="Template.xml" %}
```xml
<SqlQuery Name="AppOrderSaveSqlQuery">
  <Text>
    SELECT template.order_save_from_app({Model}::json);
  </Text>
</SqlQuery>
```
{% endcode %}

В запросе вызывается функция на сохранение заказа, в которую передаем json-объект с данными о заказе.

{% hint style="info" %}
Подробно почитать о функциях и операторах, поддерживаемых PostgreSQL для работы с JSON, можете по [ссылке](https://postgrespro.ru/docs/postgrespro/11/functions-json).
{% endhint %}

Функция сохранения заказа будет иметь вид:

```
CREATE OR REPLACE FUNCTION template.order_save_from_app(
    in_order json)
  RETURNS TABLE(order_id bigint) AS
$BODY$
DECLARE

  _order_id bigint = in_order ->> 'order_id';
  _client_id bigint = in_order->> 'client_id';

  _order_position record;

BEGIN

  UPDATE template.order O
  SET
    client_id = _client_id
  WHERE
    O.order_id = _order_id;

  FOR _order_position IN (
    SELECT
       *
    FROM
      json_to_recordset(in_order -> 'order_position') AS T(
        order_position_id bigint,
        material_id bigint,
        quantity numeric,
        unit_price numeric
      )
  )
  LOOP

    IF (_order_position.order_position_id ISNULL) THEN
    
      INSERT INTO template.order_position (
        order_id,
        material_id,
        quantity,
        unit_price
      )
      VALUES (
        _order_id,
        _order_position.material_id,
        _order_position.quantity,
        _order_position.unit_price
      );
    
    ELSE

      UPDATE template.order_position
      SET
        material_id = _order_position.material_id,
        quantity = _order_position.quantity,
        unit_price = _order_position.unit_price
      WHERE
        order_position_id = _order_position.order_position_id;

    END IF;
  END LOOP;

  RETURN  QUERY SELECT _order_id;
  
END;
$BODY$
  LANGUAGE plpgsql;
```

**Сохранение в базу**

Реализуйте функцию template.order\_save(json). Функция должна возвращать orde&#x72;_\__&#x69;d, который необходимо отлавливать на форме TemplateOrderEdit.xml и писать в параметр OrderId.

Не забывайте про работу с датой со временем и временные зоны. В JSON-строке на сервер даты со временем придут в UTC. Для приведения времени во временную зону сервера используйте функцию **public.convert\_date\_json(timestamp without time zone)**.





