Home > Blockchain >  Combine - how to proceed to decode a local json file if online fetch failed?
Combine - how to proceed to decode a local json file if online fetch failed?

Time:03-14

I have an up-to-date json file hosted online and a local json file in my Xcode workspace. I would like to proceeed to decode a locally stored file if fetching failed: MyError.fetchError e.g. for no internet connection. This is the pipeline:

func fetchAndDecode<T: Decodable>(url: URL) -> AnyPublisher<T, MyError> {
    fetchURL(url: url)
        .decode(type: T.self, decoder: JSONDecoder())
        .mapError { error in
            if let error = error as? DecodingError {
                return MyError.parsingError
            }  else {
                return MyError.fetchError //here somehow proceed to parse local json file
            }
        }
        .eraseToAnyPublisher()
}

How to achieve this ?

CodePudding user response:

.mapError is the wrong operator because it considers only the Error branch.

fetchURL returns obviously Data, so before decoding the data you have to replace the fetch error with the local data.

Before the .decode... line insert

.replaceError(with: try! Data(contentsOf: Bundle.main.url(forResource: "local", withExtension: "json")!))

and delete the .mapError operator.

local.json represents the file name of the local file in the bundle.

CodePudding user response:

I can propose an alternate but similar method to download the data and handle the error, using the async functions introduced for iOS 15.

Create a function that reads the data asynchronously and returns the data from the server if the connection worked, otherwise it will return the local JSON if a problem was found:

func getData(fromURL url: URL) async -> Data {

        let request = URLRequest(url: url)
        let (data, response) = try await URLSession.shared.data(for: request)
        
        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
                  print("HTTP response: \(response.debugDescription)")

              // Found an issue: return the local JSON
              return localJSON
              }

        // If everything is OK, return the data from the server
        return data
}

Decode the data returned:

// Use the code below in an asynchronous environment -
// either an async function or inside a Task { } closure

        let data = await getData(fromURL: url)
        do {
            let decoded = try JSONDecoder().decode(T.self, from: data)
            print("Decoded JSON: \(decoded)")
            return decoded
        } catch {
            print("Error decoding JSON: \(error), \(error.localizedDescription)")
        }
  • Related