首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >JSON解析服务

JSON解析服务
EN

Code Review用户
提问于 2020-11-16 17:21:29
回答 1查看 291关注 0票数 4

我创建了以下服务,用于发送用于解析的文件。我使用泛型,所以当我调用parse()时,我可以传递一个符合Codable的自定义类型。我不太喜欢重复使用do {} catch {},但我没有看到任何其他方法来避免这种情况。

代码语言:javascript
复制
import Foundation

enum JSONError: Error {
    case read,
         parse
}

struct JSONService {
    let fileName: String
    
    func parse<T: Decodable>(type: T.Type) -> T? {
        do {
            let fileContents = try self.getJsonContents()
            return try self.parseJson(data: fileContents, type: T.self)
        } catch {
            fatalError("\(error.localizedDescription)")
        }
    }
    
    private func getJsonContents() throws -> Data {
        if let file = Bundle.main.path(forResource: self.fileName, ofType: "json") {
            if let data = try String(contentsOfFile: file).data(using: .utf8) {
                return data
            }
        }
        
        throw JSONError.read
    }
    
    private func parseJson<T: Decodable>(data: Data, type: T.Type) throws -> T {
        do {
            return try JSONDecoder().decode(type.self, from: data)
        } catch {
            throw JSONError.parse
        }
    }
}
EN

回答 1

Code Review用户

发布于 2020-11-16 19:15:50

您的方法从嵌入在应用程序包中的JSON文件中读取对象,如果由于任何原因而失败,则使用运行时错误中止。这很好:坏的嵌入式数据是编程错误,应该尽早检测到。

parse()方法的返回类型不应该是可选的,因为它从不返回nil

自定义错误类型用于将错误从助手方法传递给main方法。这有两个缺点:

  • 它隐藏从其他方法(例如JSON解码器方法)抛出的错误(可能提供更多信息)。
  • 使用fatalError()打印的消息只显示错误类型和整数,例如致命错误:操作无法完成。(MyApp.JSONError错误0.)

第二个问题可以改进(例如,请参见堆栈如何在Swift中提供带有错误类型的本地化描述? on Stack OverFlow),但我会选择另一条路径,并在可能的情况下传递现有错误:

parseJson()中,您可以传递JSON解码器已经抛出的错误,而不是通过自定义错误对其进行跟踪:

代码语言:javascript
复制
private func parseJson<T: Decodable>(data: Data, type: T.Type) throws -> T {
    try JSONDecoder().decode(type.self, from: data)
}

getJsonContents()中,您可以使用(引发) Data(contentsOf: url)方法读取数据,这也比读取字符串并将字符串转换为数据更简单。

它仍然需要处理Bundle.main.url(forResource:withExtension:)中的错误,该方法不会抛出错误,而是返回一个可选的错误。

没有助手方法中的捕获操作,一切都可以很好地融入主方法:

代码语言:javascript
复制
func parse<T: Decodable>(type: T.Type) -> T {
    guard let url = Bundle.main.url(forResource: self.fileName, withExtension: "json") else {
        fatalError("file not found")
    }
    do {
        let data = try Data(contentsOf: url)
        return try JSONDecoder().decode(type.self, from: data)
    } catch {
        fatalError("\(error.localizedDescription)")
    }
}

您还需要打印完整的error,而不是它的localizedDescription,因为这可以提供更多的信息(并且该字符串不会显示给您的程序的用户,只是为了诊断目的)。

最后,我想知道fileName是否真的应该是一个实例变量。如果它只在parse()方法中使用,而没有其他地方使用,那么您可以将它作为静态方法的参数:

代码语言:javascript
复制
struct JSONService {
    static func parse<T: Decodable>(type: T.Type, from fileName: String) -> T {
        guard let url = Bundle.main.url(forResource: fileName, withExtension: "json") else {
            fatalError("file not found")
        }
        do {
            let data = try Data(contentsOf: url)
            return try JSONDecoder().decode(type.self, from: data)
        } catch {
            fatalError("\(error)")
        }
    }
}

它将被称为

代码语言:javascript
复制
let value = JSONService.parse(type: MyType.self, from: "filename")

将其嵌入到struct JSONService中仍然很有用,因为它为其静态方法提供了“名称空间”。如果只有静态方法,则可以将它们嵌入到enum JSONService中,因为不需要创建该类型的实例(感谢@Shadowrun提醒我注意到这一点)。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/252197

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档