Загрузите два json-файла массивов словарей. Добавьте недостающие словари из файла A в файл B

#arrays #json #swift #dictionary

#массивы #json #swift #словарь

Вопрос:

Мне нужно синхронизировать два json-файла, чтобы добавить новое содержимое из файла A (расположенного в пакете приложений) в файл B после обновления приложения.

Оба json-файла являются массивами словарей. Мне нужно повторить словари из файла A, и на основе значения «id», если словарь отсутствует в файле B, мне нужно добавить эти отсутствующие словари и сохранить файл B обратно в файловую систему.

У меня есть решение ниже, которое делает это, и, кажется, работает. Но это ТАК некрасиво! Конечно, я собрал это вместе примерно за 15 минут, весь съежившись, но я уверен, что должен быть лучший способ справиться с этим. Кроме того, я не хочу еще больше мутить воду, преобразуя эти словари в структуры или модели только для сравнения, чтобы преобразовать их обратно в dictionary -> json.

Любой совет здесь был бы отличным! Я предпочитаю чистый код, а это беспорядок.

 typealias JSON = [[String: Any]]
static private func uglySync() {
    let fileName: String = "someFileName"
    guard let sourceUrl = Bundle.main.url(forResource: fileName, withExtension: "json") else { return }
    guard let destinationDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
    let destinationUrl = destinationDirectory.appendingPathComponent("Data/"   fileName   ".json")

    do {
        let sourceData = try Data(contentsOf: sourceUrl)
        do {
            if let sourceArray = try JSONSerialization.jsonObject(with: sourceData, options: .mutableContainers) as? JSON {
                do {
                    let destinationData = try Data(contentsOf:  destinationUrl)
                    do {
                        if let destinationArray = try JSONSerialization.jsonObject(with: destinationData, options: .mutableContainers) as? JSON {
                            var mutableArray = destinationArray
                            sourceArray.forEach({ (item) in
                                if let itemId = item["id"] as? String {
                                    let foundItem = destinationArray.filter { $0["id"] as! String == itemId }.first
                                    if foundItem == nil {
                                        mutableArray.append(item)
                                    }
                                }
                            })

                            do {
                                let jsonData = try JSONSerialization.data(withJSONObject: mutableArray, options: .prettyPrinted)
                                try jsonData.write(to: destinationUrl)
                            } catch let error as NSError {
                                print("Couldn't write to file: (error.localizedDescription)")
                            }
                        } else {
                            print("Cound not process json")
                        }
                    } catch {
                        print(error.localizedDescription)
                    }
                } catch {
                    print(error.localizedDescription)
                }
            } else {
                print("Cound not process json")
            }
        } catch {
            print(error.localizedDescription)
        }
    } catch {
        print(error.localizedDescription)
    }

    // oh wow the try catches :/
}
  

Ответ №1:

Я сгруппировал преобразование файлов в JSONArray, чтобы упростить выполнение … catch. В качестве альтернативы, если вам не нужно печатать сообщение об ошибке, вы также можете выбрать необязательный try? , чтобы удалить блок do … catch.

 typealias JSONArray = [[String: Any]]

private func jsonArray(from fileURL: URL) -> JSONArray? {
    do {
        let fileData: Data = try Data(contentsOf: fileURL)
        guard let jsonArray = (try JSONSerialization.jsonObject(with: fileData, options: .mutableContainers)) as? JSONArray else {
            debugPrint("Failed to find JSON Array table")
            return nil
        }

        return jsonArray
    } catch {
        print(error.localizedDescription)
        return nil
    }
}

func sync() {
    let fileName: String = "someFileName"

    guard
        let fileURL: URL = Bundle.main.url(forResource: fileName, withExtension: "json"),
        let destinationDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first,
        let destinationURL: URL = destinationDirectory.appendingPathComponent("Data/"   fileName   ".json"),
        let sourceArray = jsonArray(from: fileURL),
        let destinationArray = jsonArray(from: destinationURL)
    else {
        return
    }

    var mutableArray = destinationArray
    let destinationIDArray = destinationArray.compactMap { $0["id"] as? String }

    mutableArray.forEach { (item) in
        if let itemId = item["id"] as? String, !(destinationIDArray.contains { $0 == itemId }) {
            mutableArray.append(item)
        }
    }

    // Update File
    do {
        let jsonData = try JSONSerialization.data(withJSONObject: mutableArray, options: .prettyPrinted)
        try jsonData.write(to: destinationURL)
    } catch {
        print("Couldn't write to file: (error.localizedDescription)")
    }
}
  

Комментарии:

1. Мне действительно нравится это решение! Я добавил это в свой проект, и после нескольких небольших настроек это отлично работает. Чистый и удобный для чтения. Спасибо!

Ответ №2:

Я думаю, вы можете поместить разные try s в один do блок.

 do {
  try function1()
  try function2()
} catch {
  print(error.localizedDescription)
}
  

Таким образом, впоследствии ваша функция может выглядеть следующим образом

     typealias JSON = [[String: Any]]
    static private func moderatelyOkSync() {
        let fileName: String = "someFileName"
        guard let sourceUrl = Bundle.main.url(forResource: fileName, withExtension: "json") else { return }
        guard let destinationDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
        let destinationUrl = destinationDirectory.appendingPathComponent("Data/"   fileName   ".json")

        do {
            let sourceData = try Data(contentsOf: sourceUrl)
            if let sourceArray = try JSONSerialization.jsonObject(with: sourceData, options: .mutableContainers) as? JSON {
                let destinationData = try Data(contentsOf:  destinationUrl)
            }
            var mutableArray = destinationArray
            sourceArray.forEach({ (item) in
                if let itemId = item["id"] as? String {
                    let foundItem = destinationArray.filter { $0["id"] as! String == itemId }.first
                    if foundItem == nil {
                        mutableArray.append(item)
                    }
                }
            })
            let jsonData = try JSONSerialization.data(withJSONObject: mutableArray, options: .prettyPrinted)
            try jsonData.write(to: destinationUrl)

        } catch {
            print(error.localizedDescription)
        }

    }
  

Комментарии:

1. Совершенно верно. Я был немного многословен. Что вы думаете об аспектах загрузки / сравнения / сохранения? Спасибо за быстрый ответ!

2. @mejim707 Сохранение и загрузка тоже выглядят нормально, возможно, извлечь их в отдельную функцию. Сравнение тоже не слишком убого.

Ответ №3:

Способ, которым я бы это сделал, — это декодировать json-файлы с помощью struct, а затем закодировать (сериализовать) их в другие файлы. Потому что код для этого будет состоять из двух строк, но сначала вам придется расположить все переменные в структуре. Вероятно, все еще не оптимально