Skip to content

Use a version of json scanner from Foundation source code to load json files #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Sources/JMESPath/Array.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/// Array storage for JMES
typealias JMESArray = [Any]

extension JMESArray {
/// return if arrays are equal by converting entries to `JMESVariable`
func equalTo(_ rhs: JMESArray) -> Bool {
guard self.count == rhs.count else { return false }
for i in 0..<self.count {
guard JMESVariable(from: self[i]) == JMESVariable(from: rhs[i]) else {
return false
}
}
return true
}
}

extension Array {
/// calculate actual index. Negative indices read backwards from end of array
func calculateIndex(_ index: Int) -> Int {
if index >= 0 {
return index
} else {
return count + index
}
}
}
6 changes: 3 additions & 3 deletions Sources/JMESPath/Ast.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// JMES expression abstract syntax tree
public indirect enum Ast: Equatable {
indirect enum Ast: Equatable {
/// compares two nodes using a comparator
case comparison(comparator: Comparator, lhs: Ast, rhs: Ast)
/// if `predicate` evaluates to a truthy value returns result from `then`
Expand Down Expand Up @@ -39,7 +39,7 @@ public 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 All @@ -66,4 +66,4 @@ public enum Comparator: Equatable, JMESSendable {
// have to force Sendable conformance as enum `.literal` uses `JMESVariable` which
// is not necessarily sendable but in the use here it is
extension Ast: @unchecked Sendable {}
#endif
#endif
43 changes: 29 additions & 14 deletions Sources/JMESPath/Expression.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif

/// 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,8 +26,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
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 JSON
Expand All @@ -34,8 +42,12 @@ public struct JMESExpression: JMESSendable {
/// - 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: some ContiguousBytes, 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,9 +58,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
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))")
}
return value
}

/// Search JSON
Expand All @@ -58,9 +73,9 @@ public struct JMESExpression: JMESSendable {
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search(json: Data, runtime: JMESRuntime = .init()) throws -> Any? {
let value = try JMESVariable.fromJson(json)
return try runtime.interpret(value, ast: self.ast).collapse()
public func search(json: String, runtime: JMESRuntime = .init()) throws -> Any? {
let value = try JMESJSON.parse(json: json)
return try runtime.interpret(JMESVariable(from: value), ast: self.ast).collapse()
}

/// Search JSON
Expand All @@ -70,9 +85,9 @@ public struct JMESExpression: JMESSendable {
/// - runtime: JMES runtime (includes functions)
/// - Throws: JMESPathError
/// - Returns: Search result
public func search(json: String, runtime: JMESRuntime = .init()) throws -> Any? {
let value = try JMESVariable.fromJson(json)
return try runtime.interpret(value, ast: self.ast).collapse()
public func search(json: some ContiguousBytes, runtime: JMESRuntime = .init()) throws -> Any? {
let value = try JMESJSON.parse(json: json)
return try runtime.interpret(JMESVariable(from: value), ast: self.ast).collapse()
}

/// Search Swift type
Expand Down
8 changes: 3 additions & 5 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 @@ -118,7 +116,7 @@ extension JMESVariable {
/// let expression = try Expression.compile(myExpression)
/// let result = try expression.search(json: myJson, runtime: runtime)
/// ```
public protocol JMESFunction {
protocol JMESFunction {
/// function signature
static var signature: FunctionSignature { get }
/// Evaluate function
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
Loading
Loading