-
Notifications
You must be signed in to change notification settings - Fork 755
Closed
Milestone
Description
Right now, JSON parsing is done via Yams.load_all
since JSON is a subset of Yaml.
That's clever, but comes with a perfomance cost. For my project, I'm using SwiftGen to parse Lottie JSON animation files into enums and that's taking almost ~3s per (incremental build).
My initial thought was to add caching support (#695). But I think we can avoid that (for now?) and do something way simpler: using JSONSerialization
instead. In my small test, this made swiftgen
to take ~750ms instead.
This is what I did - I can clean this and submit a PR, just need some guidance:
// MARK: JSON File Parser
public enum JSON {
public enum ParserError: Error, CustomStringConvertible {
case invalidFile(path: Path, reason: String)
public var description: String {
switch self {
case .invalidFile(let path, let reason):
return "Unable to parse file at \(path). \(reason)"
}
}
}
public class Parser: SwiftGenKit.Parser {
var files: [File] = []
public var warningHandler: Parser.MessageHandler?
public required init(options: [String: Any] = [:], warningHandler: Parser.MessageHandler? = nil) {
self.warningHandler = warningHandler
}
public class var defaultFilter: String {
return ".*\\.(?i:json)$"
}
public func parse(path: Path, relativeTo parent: Path) throws {
files.append(try File(path: path, relativeTo: parent))
}
}
// is it ok to duplicate this struct? I did it to avoid breaking any public APIs, but I guess I could extract it to another type and have a typealias here. Thoughts?
struct File {
let path: Path
let name: String
let documents: [Any]
init(path: Path, relativeTo parent: Path? = nil) throws {
guard let data: Data = try? path.read() else {
throw ParserError.invalidFile(path: path, reason: "Unable to read file")
}
self.path = parent.flatMap { path.relative(to: $0) } ?? path
self.name = path.lastComponentWithoutExtension
do {
let item = try JSONSerialization.jsonObject(with: data)
self.documents = [item] // why does the Yaml version take an array? is this the right thing for JSON?
} catch let error {
throw ParserError.invalidFile(path: path, reason: error.localizedDescription)
}
}
}
}
// this is pretty much the same as `Yaml.Parser`. What is the right abstraction here? Are we ok with duplicating it?
extension JSON.Parser {
public func stencilContext() -> [String: Any] {
let files = self.files
.sorted { lhs, rhs in lhs.name < rhs.name }
.map(map(file:))
return [
"files": files
]
}
private func map(file: JSON.File) -> [String: Any] {
return [
"name": file.name,
"path": file.path.string,
"documents": file.documents.map(map(document:))
]
}
private func map(document: Any) -> [String: Any] {
return [
"data": document,
"metadata": Metadata.generate(for: document)
]
}
}