В одном из проектов возникла необходимость перевести процессы импорта данных сторонних систем на микросервисную архитектуру. В качестве инструмента выбран Apache NiFi. В качестве первого подопытного выбран импорт ЕГРЮЛ ФНС.

В предыдущей статье был описан способ преобразования XML в JSON с использованием AVRO schema.

В данной статье описан способ преобразования JSON с помощью JOLT спецификации.

Используемые процессоры и контроллеры

Деление JSON на части

FlowFile, полученный на предыдущем этапе, содержит JSON с массивом выписок ЕГРЮЛ по разным организациям. Для начала разделим его на части, чтобы каждый FlowFile содержал одну выписку.

Для этого используем процессор SplitJson. Из настроек - требуется указать выражение JsonPath для разделения json на части. В данном случае $.*

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

Потренироваться можно здесь

Преобразование JSON

Полученный JSON имеет излишне сложную структуру для того, чтобы в дальнейшем хранить и обрабатывать его. Адрес и ФИО лучше объединить в одну строку, некоторые элементы перенести выше по иерархии.

JSON перед трансформацией
{
  "reportDate" : "2020-05-20",
  "ogrn" : "1234567890123",
  "ogrnDate" : "2002-12-30",
  "inn" : "1234567890",
  "kpp" : "123456789",
  "opfCode" : "12300",
  "opfName" : "Общества с ограниченной ответственностью",
  "name" : {
    "fullName" : "ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ",
    "shortName" : "ООО"
  },
  "address" : {
    "addressRF" : {
      "region" : {
        "type" : "ОБЛАСТЬ",
        "name" : "МОСКОВСКАЯ"
      },
      "district" : null,
      "town" : {
        "type" : "ГОРОД",
        "name" : "ИСТРА"
      },
      "settlement" : null,
      "street" : {
        "type" : "ПЕРЕУЛОК",
        "name" : "ВОЛОКОЛАМСКИЙ"
      },
      "index" : "143500",
      "regionCode" : "50",
      "kladr" : "500000570000011",
      "house" : null,
      "building" : null,
      "apartment" : null
    }
  },
  "termination" : null,
  "capital" : null,
  "manageOrg" : null,
  "director" : [ {
    "fl" : {
      "lastName" : "ИВАНОВ",
      "firstName" : "ИВАН",
      "patronymic" : "ИВАНОВИЧ",
      "inn" : "123456789012"
    },
    "position" : {
      "ogrnip" : null,
      "typeCode" : "02",
      "typeName" : "Руководитель юридического лица",
      "name" : "ГЕНЕРАЛЬНЫЙ ДИРЕКТОР"
    },
    "disqualification" : null
  } ],
  "founders" : {
    "founderULRF" : null,
    "founderULForeign" : null,
    "founderFL" : [ {
      "fl" : {
        "lastName" : "ИВАНОВ",
        "firstName" : "ИВАН",
        "patronymic" : "ИВАНОВИЧ",
        "inn" : "123456789012"
      },
      "capitalPart" : {
        "nominal" : 20000.0,
        "size" : {
          "percent" : 50.0,
          "decimalPart" : null,
          "simplePart" : null
        }
      }
    }, {
      "fl" : {
        "lastName" : "ПЕТРОВ",
        "firstName" : "ПЕТР",
        "patronymic" : "ПЕТРОВИЧ",
        "inn" : "123456789021"
      },
      "capitalPart" : {
        "nominal" : 20000.0,
        "size" : {
          "percent" : 50.0,
          "decimalPart" : null,
          "simplePart" : null
        }
      }
    } ],
    "founderGov" : null,
    "founderPIF" : null
  },
  "capitalPart" : null,
  "holderReestrAO" : null,
  "okved" : {
    "mainOkved" : {
      "code" : "47.11",
      "name" : "Торговля розничная преимущественно пищевыми продуктами, включая напитки, и табачными изделиями в неспециализированных магазинах"
    },
    "addOkved" : null
  }
}

Для трансформации JSON используется процессор JoltTransformJSON.

Настройки:

  • Jolt Transformation DSL - тип трансформации. В данном случае Chain - цепочка из нескольких трансформаций

  • Jolt Specification - собственно сама спецификация. Ее разбор ниже

JOLT спецификация

Собственно, сам субъект - ссылка на исходники и документацию.

Потренироваться можно здесь.

Меня интересовали операции сдвига элементов по иерархии - операция shift и преобразование самих данных - операция modify-overwrite-beta. По последней доки как таковой и нет. Исходники операции в Modifier.java, там можно посмотреть список доступных функций. Но на jolt-demo.appspot.com внизу есть примеры для этой операции. Так что методом научного тыка есть возможность прийти к решению.

JOLT спецификация
[
	{
		"operation": "modify-overwrite-beta",
		"spec": {
			"address": {
				"addressRF": {
					"region": "=concat(@(type), ' ', @(name))",
					"district": "=concat(@(type), ' ', @(name))",
					"town": "=concat(@(type), ' ', @(name))",
					"settlement": "=concat(@(type), ' ', @(name))",
					"street": "=concat(@(type), ' ', @(name))"
				}
			},
			"director": {
				"*": {
					"fl": {
						"fio": "=concat(@(1,lastName), ' ', @(1,firstName), ' ', @(1,patronymic))"
					}
				}
			},
			"founders": {
				"founderFL": {
					"*": {
						"fl": {
							"fio": "=concat(@(1,lastName), ' ', @(1,firstName), ' ', @(1,patronymic))"
						}
					}
				},
				"founderGov": {
					"*": {
						"founderImplFL": {
							"fl": {
								"fio": "=concat(@(1,lastName), ' ', @(1,firstName), ' ', @(1,patronymic))"
							}
						}
					}
				}
			}
		}
	},
	{
		"operation": "modify-overwrite-beta",
		"spec": {
			"address": {
				"addressRF": {
					"value": "=concat(@(1,index), ', ', @(1,region), ', ', @(1,district), ', ', @(1,town), ', ', @(1,settlement), ', ', @(1,street), ', ', @(1,house), ', ', @(1,building), ', ', @(1,apartment))",
					"fias": null
				}
			}
		}
	},
	{
		"operation": "shift",
		"spec": {
			"reportDate|ogrn|ogrnDate|inn|kpp|opfCode|opfName": "&",
			"name": {
				"*": "&"
			},
			"address": {
				"addressRF": {
					"kladr|regionCode|value|fias": "&2.&"
				}
			},
			"termination": {
				"method": {
					"*": "&2.&"
				},
				"*": "&1.&"
			},
			"capital": "&",
			"manageOrg": {
				"egrulData": {
					"*": "&2.&"
				}
			},
			"director": {
				"*": {
					"fl": {
						"fio|inn": "&3[&2].&"
					},
					"position": {
						"name": "&3[&2].&1",
						"*": "&3[&2].&"
					},
					"disqualification": "&2[&1].&"
				}
			},
			"founders": {
				"founderULRF|founderULForeign": {
					"*": {
						"egrulData|foreignReg": {
							"*": "&4.&3[&2].&"
						},
						"*": "&3.&2[&1].&"
					}
				},
				"founderFL": {
					"*": {
						"fl": {
							"fio|inn": "&4.&3[&2].&"
						},
						"*": "&3.&2[&1].&"
					}
				},
				"founderGov": {
					"*": {
						"govOrg": {
							"*": "&4.&3[&2].&"
						},
						"capitalPart": "&3.&2[&1].&",
						"founderImplUL": {
							"egrulData": {
								"*": "&5.&4[&3].&2.&"
							}
						},
						"founderImplFL": {
							"fl": {
								"fio|inn": "&5.&4[&3].&2.&"
							}
						}
					}
				},
				"founderPIF": {
					"*": {
						"PIFName": {
							"name": "&4.&3[&2].&1"
						},
						"manageOrg": {
							"egrulData": {
								"*": "&5.&4[&3].&"
							}
						},
						"capitalPart": "&3.&2[&1].&"
					}
				}
			},
			"capitalPart": "&",
			"holderReestrAO": {
				"egrulData": {
					"*": "&2.&"
				}
			},
			"okved": "&"
		}
	}
]

Операцию modify-overwrite-beta пришлось делать два раза, т.к. по другому собрать адрес в одну строку у меня не получилось.

Как видно, вся спецификация представляет собой массив из трех операций: две - modify-overwrite-beta и одна - shift. Описание каждой операции содержит ее тип - элемент operation и спецификацию - элемент spec.

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

Операция modify-overwrite-beta

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

Преобразование адреса

Разберем преобразование адреса.

Первый этап (см. первый блок modify-overwrite-beta) - объединить type и name для region, district, town, settlement и street. Для этого прописываем путь к элементам и в правой части для каждого пишем "=concat(@(type), ' ', @(name))" .

"address": {
				"addressRF": {
					"region": "=concat(@(type), ' ', @(name))",
					"district": "=concat(@(type), ' ', @(name))",
					"town": "=concat(@(type), ' ', @(name))",
					"settlement": "=concat(@(type), ' ', @(name))",
					"street": "=concat(@(type), ' ', @(name))"
				}
			}

Что это означает. Например, "region": "=concat(@(type), ' ', @(name))", означает: на выходе требуется получить элемент region, а в качестве его содержимого требуется получить конкатенацию содержимого элементов type и name. Причем искать эти элементы необходимо непосредственно внутри существующего элемента region, о чем говорит конструкция @(type).

Второй этап (см. второй блок modify-overwrite-beta) - объединить составляющие адреса в одну строку и записать в элемент value.

"address": {
				"addressRF": {
					"value": "=concat(@(1,index), ', ', @(1,region), ', ', @(1,district), ', ', @(1,town), ', ', @(1,settlement), ', ', @(1,street), ', ', @(1,house), ', ', @(1,building), ', ', @(1,apartment))",
					"fias": null
				}
			}

Здесь примерно то же самое, но теперь используется конструкция вида @(1,index). Она означает, что для поиска элемента index необходимо подняться на один уровень вверх от текущего и искать его там. Т.е. от уровня value необходимо перейти к уровню addressRF, и в пределах addressRF найти элемент index.

Следует обратить внимание, что не должно быть пробелов между = и concat, а также в @(1,index).

Элемент fias был добавлен в надежде в дальнейшем осуществить поиск кода ФИАС по адресу в каком-нибудь стороннем сервисе.

На этом преобразование адреса завершено. Про операцию shift будет ниже.

Объединение ФИО для физически�� лиц

Объединение ФИО для физических лиц осуществляется аналогичным образом. Здесь обращает на себя внимание только наличие селектора "*" в левой части. Он используется, т.к. содержимое элемента director представляет собой массив, а это отдельный уровень иерархии.

"director": {
				"*": {
					"fl": {
						"fio": "=concat(@(1,lastName), ' ', @(1,firstName), ' ', @(1,patronymic))"
					}
				}
			}

Операция shift

В блок shift на вход поступает следующий JSON.

Промежуточный JSON
{
  "reportDate" : "2020-05-20",
  "ogrn" : "1234567890123",
  "ogrnDate" : "2002-12-30",
  "inn" : "1234567890",
  "kpp" : "123456789",
  "opfCode" : "12300",
  "opfName" : "Общества с ограниченной ответственностью",
  "name" : {
    "fullName" : "ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ",
    "shortName" : "ООО"
  },
  "address" : {
    "addressRF" : {
      "region" : "ОБЛАСТЬ МОСКОВСКАЯ",
      "district" : " ",
      "town" : "ГОРОД ИСТРА",
      "settlement" : " ",
      "street" : "ПЕРЕУЛОК ВОЛОКОЛАМСКИЙ",
      "index" : "143500",
      "regionCode" : "50",
      "kladr" : "500000570000011",
      "house" : null,
      "building" : null,
      "apartment" : null,
      "value" : "143500, ОБЛАСТЬ МОСКОВСКАЯ,  , ГОРОД ИСТРА,  , ПЕРЕУЛОК ВОЛОКОЛАМСКИЙ, , , ",
      "fias" : null
    }
  },
  "termination" : null,
  "capital" : null,
  "manageOrg" : null,
  "director" : [ {
    "fl" : {
      "lastName" : "ИВАНОВ",
      "firstName" : "ИВАН",
      "patronymic" : "ИВАНОВИЧ",
      "inn" : "123456789012",
      "fio" : "ИВАНОВ ИВАН ИВАНОВИЧ"
    },
    "position" : {
      "ogrnip" : null,
      "typeCode" : "02",
      "typeName" : "Руководитель юридического лица",
      "name" : "ГЕНЕРАЛЬНЫЙ ДИРЕКТОР"
    },
    "disqualification" : null
  } ],
  "founders" : {
    "founderULRF" : null,
    "founderULForeign" : null,
    "founderFL" : [ {
      "fl" : {
        "lastName" : "ИВАНОВ",
        "firstName" : "ИВАН",
        "patronymic" : "ИВАНОВИЧ",
        "inn" : "123456789012",
        "fio" : "ИВАНОВ ИВАН ИВАНОВИЧ"
      },
      "capitalPart" : {
        "nominal" : 20000,
        "size" : {
          "percent" : 50,
          "decimalPart" : null,
          "simplePart" : null
        }
      }
    }, {
      "fl" : {
        "lastName" : "ПЕТРОВ",
        "firstName" : "ПЕТР",
        "patronymic" : "ПЕТРОВИЧ",
        "inn" : "123456789021",
        "fio" : "ПЕТРОВ ПЕТР ПЕТРОВИЧ"
      },
      "capitalPart" : {
        "nominal" : 20000,
        "size" : {
          "percent" : 50,
          "decimalPart" : null,
          "simplePart" : null
        }
      }
    } ],
    "founderGov" : null,
    "founderPIF" : null
  },
  "capitalPart" : null,
  "holderReestrAO" : null,
  "okved" : {
    "mainOkved" : {
      "code" : "47.11",
      "name" : "Торговля розничная преимущественно пищевыми продуктами, включая напитки, и табачными изделиями в неспециализированных магазинах"
    },
    "addOkved" : null
  }
}

Как видно, остались лишние элементы - например, данные адреса, фамилия, имя отчество. Стоит отметить, что если в операции modify-overwrite-beta не указано преобразование для элемента, то он переносится в неизменном виде. В отличие от этого, в операции shift - если преобразование для элемента не указано, то он будет удален.

Описание операции shift
{
		"operation": "shift",
		"spec": {
			"reportDate|ogrn|ogrnDate|inn|kpp|opfCode|opfName": "&",
			"name": {
				"*": "&"
			},
			"address": {
				"addressRF": {
					"kladr|regionCode|value|fias": "&2.&"
				}
			},
			"termination": {
				"method": {
					"*": "&2.&"
				},
				"*": "&1.&"
			},
			"capital": "&",
			"manageOrg": {
				"egrulData": {
					"*": "&2.&"
				}
			},
			"director": {
				"*": {
					"fl": {
						"fio|inn": "&3[&2].&"
					},
					"position": {
						"name": "&3[&2].&1",
						"*": "&3[&2].&"
					},
					"disqualification": "&2[&1].&"
				}
			},
			"founders": {
				"founderULRF|founderULForeign": {
					"*": {
						"egrulData|foreignReg": {
							"*": "&4.&3[&2].&"
						},
						"*": "&3.&2[&1].&"
					}
				},
				"founderFL": {
					"*": {
						"fl": {
							"fio|inn": "&4.&3[&2].&"
						},
						"*": "&3.&2[&1].&"
					}
				},
				"founderGov": {
					"*": {
						"govOrg": {
							"*": "&4.&3[&2].&"
						},
						"capitalPart": "&3.&2[&1].&",
						"founderImplUL": {
							"egrulData": {
								"*": "&5.&4[&3].&2.&"
							}
						},
						"founderImplFL": {
							"fl": {
								"fio|inn": "&5.&4[&3].&2.&"
							}
						}
					}
				},
				"founderPIF": {
					"*": {
						"PIFName": {
							"name": "&4.&3[&2].&1"
						},
						"manageOrg": {
							"egrulData": {
								"*": "&5.&4[&3].&"
							}
						},
						"capitalPart": "&3.&2[&1].&"
					}
				}
			},
			"capitalPart": "&",
			"holderReestrAO": {
				"egrulData": {
					"*": "&2.&"
				}
			},
			"okved": "&"
		}
	}

В операции shift присутствуют левая и правая часть инструкции. Левая часть указывает, где брать данные, а правая указывает путь, куда их разместить. Путь представляет собой цепочку наименований элементов, разделенных точкой. С помощью знака & осуществляется подстановка наименований существующих элементов. Исчисление начинается с того элемента, который указан в левой части, ему соответствует &0. Ноль при этом можно опустить. Выше него по иерархии будет &1, и т.д. К знакам & можно добавлять префиксы и суффиксы - например, pre-&-post. Т.е. если & соответствует элементу name, то на выходе получим pre-name-post. Результирующая цепочка элементов размещается в корне иерархии. Рассмотрим на примерах.

Самое простое - "reportDate|ogrn|ogrnDate|inn|kpp|opfCode|opfName": "&". Будет взят каждый из перечисленных элементов, и они будут размещены в корне. Перечисление осуществляется с помощью |.

Далее переносим fullName и shortName на один уровень вверх с помощью инструкции "name": { "*": "&" }.
"*" означает, что требуется выбрать содержимое всех элементов, вложенных в name.
"&" означает, что их требуется разместить в корне иерархии.

Следующее - перенос данных адреса.

"address": {
				"addressRF": {
					"kladr|regionCode|value|fias": "&2.&"
				}
			}

Здесь мы указываем нужные элементы. Ненужные не указываем. Инструкция для размещения - "&2.&". Она означает, что требуется составить цепочки из нулевого и второго уровня, минуя первый. &2 соответствует элементу address, а & - элементам из перечисления. &1 соответствует элементу addressRF, он будет удален из иерархии. Т.обр. будут составлены четыре цепочки: address.kladr, address.regionCode, address.value и address.fias. И все они буду размещены в корне результирующего JSON.

Массивы разберем на примере данных о директоре

"director" : [ {
    "fl" : {
      "lastName" : "ИВАНОВ",
      "firstName" : "ИВАН",
      "patronymic" : "ИВАНОВИЧ",
      "inn" : "123456789012",
      "fio" : "ИВАНОВ ИВАН ИВАНОВИЧ"
    },
    "position" : {
      "ogrnip" : null,
      "typeCode" : "02",
      "typeName" : "Руководитель юридического лица",
      "name" : "ГЕНЕРАЛЬНЫЙ ДИРЕКТОР"
    },
    "disqualification" : null
  } ]

Нужно убрать lastName, firstName и patronymic.
inn и fio перенести на один уровень выше.
ogrnip, typeCode и typeName также перенести на один уровень выше.
Значение name установить в качестве значения position.
disqualification оставить без изменений.

В общем-то алгоритм действий тот же самый, но следуют помнить, что массив - это отдельный уровень иерархии. Когда работаем с массивом, то в цепочке иерархии соответствующий ему & должен быть помещен в квадратные скобки - [&].

"director": {
				"*": {
					"fl": {
						"fio|inn": "&3[&2].&"
					},
					"position": {
						"name": "&3[&2].&1",
						"*": "&3[&2].&"
					},
					"disqualification": "&2[&1].&"
				}
			}

Например, fio и inn. Для них цепочка &3[&2].&. Точку перед открывающей квадратной скобкой можно опустить. Получаем: &3 - соответствует элементу director, [&2] - соответствует уровню элементов массива, & - сами fio и inn.

Элемент name в position. &3 - соответствует элементу director, [&2] - соответствует уровню элементов массива, &1 - соответствует элементу position. &, соответствующий самому элементу name отсутствует, значит его содержимое будет перенесено в position.

Остальные элементы в position просто переносятся на один уровень вверх. disqualification остается без изменений.

Далее используются аналогичные конструкции.

Пример

Ну и напоследок продублирую исходный JSON, JOLT спецификацию и результирующий JSON

Исходный JSON
{
  "reportDate": "2020-05-20",
  "ogrn": "1234567890123",
  "ogrnDate": "2002-12-30",
  "inn": "1234567890",
  "kpp": "123456789",
  "opfCode": "12300",
  "opfName": "Общества с ограниченной ответственностью",
  "name": {
    "fullName": "ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ",
    "shortName": "ООО"
  },
  "address": {
    "addressRF": {
      "region": {
        "type": "ОБЛАСТЬ",
        "name": "МОСКОВСКАЯ"
      },
      "district": null,
      "town": {
        "type": "ГОРОД",
        "name": "ИСТРА"
      },
      "settlement": null,
      "street": {
        "type": "ПЕРЕУЛОК",
        "name": "ВОЛОКОЛАМСКИЙ"
      },
      "index": "143500",
      "regionCode": "50",
      "kladr": "500000570000011",
      "house": null,
      "building": null,
      "apartment": null
    }
  },
  "termination": null,
  "capital": null,
  "manageOrg": null,
  "director": [
    {
      "fl": {
        "lastName": "ИВАНОВ",
        "firstName": "ИВАН",
        "patronymic": "ИВАНОВИЧ",
        "inn": "123456789012"
      },
      "position": {
        "ogrnip": null,
        "typeCode": "02",
        "typeName": "Руководитель юридического лица",
        "name": "ГЕНЕРАЛЬНЫЙ ДИРЕКТОР"
      },
      "disqualification": null
    }
  ],
  "founders": {
    "founderULRF": null,
    "founderULForeign": null,
    "founderFL": [
      {
        "fl": {
          "lastName": "ИВАНОВ",
          "firstName": "ИВАН",
          "patronymic": "ИВАНОВИЧ",
          "inn": "123456789012"
        },
        "capitalPart": {
          "nominal": 20000,
          "size": {
            "percent": 50,
            "decimalPart": null,
            "simplePart": null
          }
        }
      },
      {
        "fl": {
          "lastName": "ПЕТРОВ",
          "firstName": "ПЕТР",
          "patronymic": "ПЕТРОВИЧ",
          "inn": "123456789021"
        },
        "capitalPart": {
          "nominal": 20000,
          "size": {
            "percent": 50,
            "decimalPart": null,
            "simplePart": null
          }
        }
      }
    ],
    "founderGov": null,
    "founderPIF": null
  },
  "capitalPart": null,
  "holderReestrAO": null,
  "okved": {
    "mainOkved": {
      "code": "47.11",
      "name": "Торговля розничная преимущественно пищевыми продуктами, включая напитки, и табачными изделиями в неспециализированных магазинах"
    },
    "addOkved": null
  }
}
JOLT спецификация
[
	{
		"operation": "modify-overwrite-beta",
		"spec": {
			"address": {
				"addressRF": {
					"region": "=concat(@(type), ' ', @(name))",
					"district": "=concat(@(type), ' ', @(name))",
					"town": "=concat(@(type), ' ', @(name))",
					"settlement": "=concat(@(type), ' ', @(name))",
					"street": "=concat(@(type), ' ', @(name))"
				}
			},
			"director": {
				"*": {
					"fl": {
						"fio": "=concat(@(1,lastName), ' ', @(1,firstName), ' ', @(1,patronymic))"
					}
				}
			},
			"founders": {
				"founderFL": {
					"*": {
						"fl": {
							"fio": "=concat(@(1,lastName), ' ', @(1,firstName), ' ', @(1,patronymic))"
						}
					}
				},
				"founderGov": {
					"*": {
						"founderImplFL": {
							"fl": {
								"fio": "=concat(@(1,lastName), ' ', @(1,firstName), ' ', @(1,patronymic))"
							}
						}
					}
				}
			}
		}
	},
	{
		"operation": "modify-overwrite-beta",
		"spec": {
			"address": {
				"addressRF": {
					"value": "=concat(@(1,index), ', ', @(1,region), ', ', @(1,district), ', ', @(1,town), ', ', @(1,settlement), ', ', @(1,street), ', ', @(1,house), ', ', @(1,building), ', ', @(1,apartment))",
					"fias": null
				}
			}
		}
	},
	{
		"operation": "shift",
		"spec": {
			"reportDate|ogrn|ogrnDate|inn|kpp|opfCode|opfName": "&",
			"name": {
				"*": "&"
			},
			"address": {
				"addressRF": {
					"kladr|regionCode|value|fias": "&2.&"
				}
			},
			"termination": {
				"method": {
					"*": "&2.&"
				},
				"*": "&1.&"
			},
			"capital": "&",
			"manageOrg": {
				"egrulData": {
					"*": "&2.&"
				}
			},
			"director": {
				"*": {
					"fl": {
						"fio|inn": "&3[&2].&"
					},
					"position": {
						"name": "&3[&2].&1",
						"*": "&3[&2].&"
					},
					"disqualification": "&2[&1].&"
				}
			},
			"founders": {
				"founderULRF|founderULForeign": {
					"*": {
						"egrulData|foreignReg": {
							"*": "&4.&3[&2].&"
						},
						"*": "&3.&2[&1].&"
					}
				},
				"founderFL": {
					"*": {
						"fl": {
							"fio|inn": "&4.&3[&2].&"
						},
						"*": "&3.&2[&1].&"
					}
				},
				"founderGov": {
					"*": {
						"govOrg": {
							"*": "&4.&3[&2].&"
						},
						"capitalPart": "&3.&2[&1].&",
						"founderImplUL": {
							"egrulData": {
								"*": "&5.&4[&3].&2.&"
							}
						},
						"founderImplFL": {
							"fl": {
								"fio|inn": "&5.&4[&3].&2.&"
							}
						}
					}
				},
				"founderPIF": {
					"*": {
						"PIFName": {
							"name": "&4.&3[&2].&1"
						},
						"manageOrg": {
							"egrulData": {
								"*": "&5.&4[&3].&"
							}
						},
						"capitalPart": "&3.&2[&1].&"
					}
				}
			},
			"capitalPart": "&",
			"holderReestrAO": {
				"egrulData": {
					"*": "&2.&"
				}
			},
			"okved": "&"
		}
	}
]
Результирующий JSON
{
  "reportDate" : "2020-05-20",
  "ogrn" : "1234567890123",
  "ogrnDate" : "2002-12-30",
  "inn" : "1234567890",
  "kpp" : "123456789",
  "opfCode" : "12300",
  "opfName" : "Общества с ограниченной ответственностью",
  "fullName" : "ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ",
  "shortName" : "ООО",
  "address" : {
    "kladr" : "500000570000011",
    "regionCode" : "50",
    "value" : "143500, ОБЛАСТЬ МОСКОВСКАЯ,  , ГОРОД ИСТРА,  , ПЕРЕУЛОК ВОЛОКОЛАМСКИЙ, , , ",
    "fias" : null
  },
  "capital" : null,
  "director" : [ {
    "fio" : "ИВАНОВ ИВАН ИВАНОВИЧ",
    "inn" : "123456789012",
    "ogrnip" : null,
    "typeCode" : "02",
    "typeName" : "Руководитель юридического лица",
    "position" : "ГЕНЕРАЛЬНЫЙ ДИРЕКТОР",
    "disqualification" : null
  } ],
  "founders" : {
    "founderFL" : [ {
      "fio" : "ИВАНОВ ИВАН ИВАНОВИЧ",
      "inn" : "123456789012",
      "capitalPart" : {
        "nominal" : 20000,
        "size" : {
          "percent" : 50,
          "decimalPart" : null,
          "simplePart" : null
        }
      }
    }, {
      "fio" : "ПЕТРОВ ПЕТР ПЕТРОВИЧ",
      "inn" : "123456789021",
      "capitalPart" : {
        "nominal" : 20000,
        "size" : {
          "percent" : 50,
          "decimalPart" : null,
          "simplePart" : null
        }
      }
    } ]
  },
  "capitalPart" : null,
  "okved" : {
    "mainOkved" : {
      "code" : "47.11",
      "name" : "Торговля розничная преимущественно пищевыми продуктами, включая напитки, и табачными изделиями в неспециализированных магазинах"
    },
    "addOkved" : null
  }
}

Далее

Далее получившийся JSON следует куда-то разместить для хранения и дальнейшего использования. Но это выходит за рамки повествования. Тут уж кому что удобно.