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

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

Работу с JSON будем рассматривать на примере карточки заказа, так как заказ имеет вложенные сущности и потенциально может иметь больше уровней вложенности. Теперь будет интересно увидеть, как изменится форма с использованием 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:

TemplateOrderEdit.xml
<MyObject Name="OrderDictionaryVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>
    <Structure Type="Dictionary">
      <Key Name=""></Key>
    </Structure>
  </Value>
</MyObject>

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

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

<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:

TemplateOrderEdit.xml
<Command Name="OrderDictionaryVariableRefreshValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="OrderDictionaryVariable">
    <Property Name="Refresh" />
  </Object>
</Command>

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

TemplateOrderEdit.xml
<Command Name="OrderDictionaryVariableSerializeToJsonCommand" Type="SerializeToJsonCommand" Assembly="Commands">
  <Variable>
    <Object Name="OrderDictionaryVariable" />
  </Variable>
</Command>

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

Сохранение изменений

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

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

<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_объекта и команду передачи данных на сервер:

TemplateOrderEdit.xml
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <Command Name="OrderDictionaryVariableRefreshValueSetCommand" />
    <Command Name="OrderDictionaryVariableSerializeToJsonCommand" />

    <Command Name="OrderUpdateSaveCommand" />
    <Command Name="UpdatedTrueValueSetCommand" />
  </Commands>
</Command>

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

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

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

Подробно почитать о функциях и операторах, поддерживаемых PostgreSQL для работы с JSON, можете по ссылке.

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

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). Функция должна возвращать order_id, который необходимо отлавливать на форме TemplateOrderEdit.xml и писать в параметр OrderId.

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

Last updated