我创建了以下服务,用于发送用于解析的文件。我使用泛型,所以当我调用parse()时,我可以传递一个符合Codable的自定义类型。我不太喜欢重复使用do {} catch {},但我没有看到任何其他方法来避免这种情况。
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
}
}
}发布于 2020-11-16 19:15:50
您的方法从嵌入在应用程序包中的JSON文件中读取对象,如果由于任何原因而失败,则使用运行时错误中止。这很好:坏的嵌入式数据是编程错误,应该尽早检测到。
parse()方法的返回类型不应该是可选的,因为它从不返回nil。
自定义错误类型用于将错误从助手方法传递给main方法。这有两个缺点:
fatalError()打印的消息只显示错误类型和整数,例如致命错误:操作无法完成。(MyApp.JSONError错误0.)第二个问题可以改进(例如,请参见堆栈如何在Swift中提供带有错误类型的本地化描述? on Stack OverFlow),但我会选择另一条路径,并在可能的情况下传递现有错误:
在parseJson()中,您可以传递JSON解码器已经抛出的错误,而不是通过自定义错误对其进行跟踪:
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:)中的错误,该方法不会抛出错误,而是返回一个可选的错误。
没有助手方法中的捕获操作,一切都可以很好地融入主方法:
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()方法中使用,而没有其他地方使用,那么您可以将它作为静态方法的参数:
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)")
}
}
}它将被称为
let value = JSONService.parse(type: MyType.self, from: "filename")将其嵌入到struct JSONService中仍然很有用,因为它为其静态方法提供了“名称空间”。如果只有静态方法,则可以将它们嵌入到enum JSONService中,因为不需要创建该类型的实例(感谢@Shadowrun提醒我注意到这一点)。
https://codereview.stackexchange.com/questions/252197
复制相似问题