Слияние двух сложных объектов JSON - PullRequest
2 голосов
/ 05 апреля 2019

Я хочу объединить два объекта JSON в новый.Я пытаюсь использовать jsonmerge с полной схемой json, но я не знаю, как правильно настроить стратегии слияния.Я почти уверен, что это можно сделать.

Код:

import json
from jsonmerge import Merger
from jsonschema import validate
full_build = {
         "captures": [
            {
               "compiler": "gnu",
               "executable": "gcc",
               "cmd": ["gcc", "options", "file1.cpp"],
               "cwd": ".",
               "env": ["A=1", "B=2"],
            },
            {
               "compiler": "gnu",
               "executable": "gcc",
               "cmd": ["gcc", "options", "file2.cpp"],
               "cwd": ".",
               "env": ["A=1", "B=2"],
            }
         ]
}
incremental_build = {
         "captures": [
            {
               "compiler": "gnu",
               "executable": "gcc",
               "cmd": ["gcc", "new options", "file2.cpp"],
               "cwd": ".",
               "env": ["A=1", "NEW=2"],
            },
            {
               "compiler": "gnu",
               "executable": "gcc",
               "cmd": ["gcc", "options", "file3.cpp"],
               "cwd": ".",
               "env": ["A=1", "B=2"],
            }
         ]
}
schema = {
   "type" : "object",
   "properties" : {
      "captures": {
         "type" : "array",
         "items" : {
            "type" : "object",
            "properties" : {
               "cmd" : {
                  "type" : "array",
                  "items" : {"type" : "string"},
               },
               "compiler" : {"type" : "string"},
               "cwd" : {"type" : "string"},
               "env" : {
                  "type" : "array",
                  "items" : {"type" : "string"},
               },
               "executable" : {"type" : "string"},
            }
         }
      }
   }
}
validate(instance=full_build, schema=schema)

mergeSchema = schema
merger = Merger(mergeSchema)
result = merger.merge(full_build, incremental_build)
print(json.dumps(result, indent=3))

Результат:

{
   "captures": [
      {
         "compiler": "gnu",
         "executable": "gcc",
         "cmd": [
            "gcc",
            "options",
            "file3.cpp"
         ],
         "cwd": ".",
         "env": [
            "A=1",
            "B=2"
         ]
      }
   ]
}

Ожидаемый результат:

{
   "captures": [
      {
         "compiler": "gnu",
         "executable": "gcc",
         "cmd": [
            "gcc",
            "options",
            "file1.cpp"
         ],
         "cwd": ".",
         "env": [
            "A=1",
            "B=2"
         ]
      },
      {
         "compiler": "gnu",
         "executable": "gcc",
         "cmd": [
            "gcc",
            "new options",
            "file2.cpp"
         ],
         "cwd": ".",
         "env": [
            "A=1",
            "NEW=2"
         ]
      },
      {
         "compiler": "gnu",
         "executable": "gcc",
         "cmd": [
            "gcc",
            "options",
            "file3.cpp"
         ],
         "cwd": ".",
         "env": [
            "A=1",
            "B=2"
         ]
      }
   ]
}

ТамЕсть много вещей, которые нужно учитывать (например, иметь больше или меньше параметров / переменных среды, чем раньше), но я думаю, с небольшим намеком мне удастся завершить задачу.Я действительно не хочу жестко его кодировать.

И нет, я не могу изменить структуру json: (.

Справочная информация: я хочу объединить выходные данные обертки сборки SonarQube, потому что я не хочувыполнить полную сборку, чтобы получить все файлы в вывод обёртки.

Ответы [ 2 ]

2 голосов
/ 05 апреля 2019

У вас есть два массива объектов JSON, и на их основе вы хотите создать один массив.

В вашем примере иногда кажется, что вы хотите, чтобы объекты из incremental_build перезаписывали объекты из full_build(есть только один объект, который упоминает file2.cpp в конечном массиве), но иногда вы этого не делаете (объект с file3.cpp не перезаписывает объект с file1.cpp).

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

Для достижения этого вы можете использовать следующую схему:

schema = {
   "properties" : {
      "captures": {
         "mergeStrategy": "arrayMergeById",
         "mergeOptions": {
            "idRef": "/cmd/2"
         },
         "items": {
            "mergeStrategy": "overwrite"
         }
      }
   }
}

merger = Merger(schema)
result = merger.merge(full_build, incremental_build)

Вам не нужна полная схема, если вы не хотите также проверить свой JSON.jsonmerge сам по себе заботится только об информации стратегии слияния.

Приведенная выше схема указывает, что массив со свойством захватывает в объекте верхнего уровня должен быть объединен с использованием arrayMergeByIdстратегия.Эта стратегия объединяет элементы массива на основе значения, на которое указывает ссылка idRef.В вашем примере имя файла является третьим элементом свойства cmd (указатели JSON используют индексацию с нуля).

arrayMergeById объединяет соответствующие элементы массива на основе своих собственных схем.По умолчанию они будут объединены с использованием стратегии objectMerge.Это приведет к неверному результату в случае, если элемент в incremental_build будет отсутствовать свойство, которое присутствует в соответствующем элементе full_build.Следовательно, приведенная выше схема также определяет стратегию overwrite для всех элементов массива captures.

2 голосов
/ 05 апреля 2019

Кажется, вам вообще не нужна никакая сложная операция слияния.По сути, вы хотите объединить списки «захватов» из обеих структур в новую структуру, которая содержит все из них.Это может быть достигнуто путем создания копии и последующего простого расширения списка:

full_build = ...
incremental_build = ...
combined = copy.deepcopy(full_build)
combined['captures'].extend(incremental_build['captures'])

Если вы хотите «дедуплицировать» на основе какого-либо атрибута, например имени файла, вы можете использовать что-то вроде этого:

def get_filename_from_capture(cmd):
    return cmd["cmd"][-1]


all_captures = full_build["captures"] + incremental_build["captures"]
captures_by_filename = {
    get_filename_from_capture(capture): capture for capture in all_captures
}

combined = copy.deepcopy(full_build)
combined["captures"] = list(captures_by_filename.values())
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...