Skip to content

Commit

Permalink
All JSON loading done by JSON scanner
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-fowler committed Dec 14, 2024
1 parent 64b798f commit 6df4b72
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 598 deletions.
1 change: 1 addition & 0 deletions Sources/JMESPath/Array.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// Array storage for JMES
typealias JMESArray = [Any]

extension JMESArray {
Expand Down
2 changes: 1 addition & 1 deletion Sources/JMESPath/Ast.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ indirect enum Ast: Equatable {
}

/// Comparator used in comparison AST nodes
public enum Comparator: Equatable, JMESSendable {
public enum Comparator: Equatable, Sendable {
case equal
case notEqual
case lessThan
Expand Down
59 changes: 14 additions & 45 deletions Sources/JMESPath/Expression.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import Foundation

/// JMES Expression
///
/// Holds a compiled JMES expression and allows you to search Json text or a type already in memory
public struct JMESExpression: JMESSendable {
public struct JMESExpression: Sendable {
let ast: Ast

public static func compile(_ text: String) throws -> Self {
Expand All @@ -22,20 +20,12 @@ public struct JMESExpression: JMESSendable {
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search<Value>(json: Data, as: Value.Type = Value.self, runtime: JMESRuntime = .init()) throws -> Value? {
try self.search(json: json, runtime: runtime) as? Value
}

/// Search JSON
///
/// - Parameters:
/// - any: JSON to search
/// - as: Swift type to return
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search<Value>(json: String, as: Value.Type = Value.self, runtime: JMESRuntime = .init()) throws -> Value? {
try self.search(json: json, runtime: runtime) as? Value
public func search<Value>(json: String, as: Value.Type = Value.self, runtime: JMESRuntime = .init()) throws -> Value {
let searchResult = try self.search(json: json, runtime: runtime)
guard let value = searchResult as? Value else {
throw JMESPathError.runtime("Expected \(Value.self)) but got a \(type(of: searchResult))")
}
return value
}

/// Search Swift type
Expand All @@ -46,27 +36,12 @@ public struct JMESExpression: JMESSendable {
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search<Value>(object: Any, as: Value.Type = Value.self, runtime: JMESRuntime = .init()) throws -> Value? {
let value = try self.search(object: object, runtime: runtime)
return value as? Value
}

/// Search JSON
///
/// - Parameters:
/// - any: JSON to search
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search(json: Data, runtime: JMESRuntime = .init()) throws -> Any? {
let variable = try json.withBufferView { view -> JMESVariable? in
var scanner = JSONScanner(bytes: view, options: .init())
let map = try scanner.scan()
guard let value = map.loadValue(at: 0) else { return nil }
return try JMESJSONVariable(value: value).getJMESVariable(map)
public func search<Value>(object: Any, as: Value.Type = Value.self, runtime: JMESRuntime = .init()) throws -> Value {
let searchResult = try self.search(object: object, runtime: runtime)
guard let value = searchResult as? Value else {
throw JMESPathError.runtime("Expected \(Value.self)) but got a \(type(of: searchResult))")
}
guard let variable else { return nil }
return try runtime.interpret(variable, ast: self.ast).collapse()
return value
}

/// Search JSON
Expand All @@ -77,14 +52,8 @@ public struct JMESExpression: JMESSendable {
/// - Throws: JMESPathError
/// - Returns: Search result
public func search(json: String, runtime: JMESRuntime = .init()) throws -> Any? {
let variable = try json.withBufferView { view -> JMESVariable? in
var scanner = JSONScanner(bytes: view, options: .init())
let map = try scanner.scan()
guard let value = map.loadValue(at: 0) else { return nil }
return try JMESJSONVariable(value: value).getJMESVariable(map)
}
guard let variable else { return nil }
return try runtime.interpret(variable, ast: self.ast).collapse()
let value = try JMESJSON.parse(json: json)
return try runtime.interpret(JMESVariable(from: value), ast: self.ast).collapse()
}

/// Search Swift type
Expand Down
6 changes: 2 additions & 4 deletions Sources/JMESPath/Functions.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Foundation

/// Used to validate arguments of a function before it is run
public struct FunctionSignature {
/// Function argument used in function signature to verify arguments
Expand Down Expand Up @@ -310,7 +308,7 @@ struct MapFunction: JMESFunction {
static func evaluate(args: [JMESVariable], runtime: JMESRuntime) throws -> JMESVariable {
switch (args[0], args[1]) {
case (.expRef(let ast), .array(let array)):
let results = try array.map { try runtime.interpret(JMESVariable(from: $0), ast: ast).collapse() ?? NSNull() }
let results = try array.map { try runtime.interpret(JMESVariable(from: $0), ast: ast).collapse() ?? JMESNull() }
return .array(results)
default:
preconditionFailure()
Expand Down Expand Up @@ -665,7 +663,7 @@ struct ToArrayFunction: JMESFunction {
case .array:
return args[0]
default:
return .array([args[0].collapse() ?? NSNull()])
return .array([args[0].collapse() ?? JMESNull()])
}
}
}
Expand Down
9 changes: 4 additions & 5 deletions Sources/JMESPath/Interpreter.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Foundation

/// Extend runtime with intepret function
extension JMESRuntime {
/// Interpret Ast given object to search
/// - Parameters:
Expand Down Expand Up @@ -78,7 +77,7 @@ extension JMESRuntime {
for element in array {
let currentResult = try interpret(.init(from: element), ast: rhs)
if currentResult != .null {
collected.append(currentResult.collapse() ?? NSNull())
collected.append(currentResult.collapse() ?? JMESNull())
}
}
return .array(collected)
Expand Down Expand Up @@ -108,7 +107,7 @@ extension JMESRuntime {
}
var collected: JMESArray = []
for node in elements {
collected.append(try self.interpret(data, ast: node).collapse() ?? NSNull())
collected.append(try self.interpret(data, ast: node).collapse() ?? JMESNull())
}
return .array(collected)

Expand All @@ -119,7 +118,7 @@ extension JMESRuntime {
var collected: JMESObject = [:]
for element in elements {
let valueResult = try self.interpret(data, ast: element.value)
collected[element.key] = valueResult.collapse() ?? NSNull()
collected[element.key] = valueResult.collapse() ?? JMESNull()
}
return .object(collected)

Expand Down
44 changes: 44 additions & 0 deletions Sources/JMESPath/JMESPath+Data.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Foundation

/// JMESExpression extensions for Data
extension JMESExpression {
/// Search JSON
///
/// - Parameters:
/// - any: JSON to search
/// - as: Swift type to return
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search<Value>(json: Data, as: Value.Type = Value.self, runtime: JMESRuntime = .init()) throws -> Value {
let searchResult = try self.search(json: json, runtime: runtime)
guard let value = searchResult as? Value else {
throw JMESPathError.runtime("Expected \(Value.self)) but got a \(type(of: searchResult))")
}
return value
}

/// Search JSON
///
/// - Parameters:
/// - any: JSON to search
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search(json: Data, runtime: JMESRuntime = .init()) throws -> Any? {
let value = try JMESJSON.parse(json: json)
return try runtime.interpret(JMESVariable(from: value), ast: self.ast).collapse()
}
}

/// Parse json in the form of Data
extension JMESJSON {
static func parse(json: Data) throws -> Any {
try json.withBufferView { view in
var scanner = JSONScanner(bytes: view, options: .init())
let map = try scanner.scan()
guard let value = map.loadValue(at: 0) else { throw JMESPathError.runtime("Empty JSON file") }
return try JMESJSONVariable(value: value).getValue(map)
}
}
}
Loading

0 comments on commit 6df4b72

Please sign in to comment.