| 
 | 1 | +//  | 
 | 2 | +//  JSONPath.swift  | 
 | 3 | +//  FlyingFox  | 
 | 4 | +//  | 
 | 5 | +//  Created by Simon Whitty on 29/05/2023.  | 
 | 6 | +//  Copyright © 2023 Simon Whitty. All rights reserved.  | 
 | 7 | +//  | 
 | 8 | +//  Distributed under the permissive MIT license  | 
 | 9 | +//  Get the latest version from here:  | 
 | 10 | +//  | 
 | 11 | +//  https://github.com/swhitty/FlyingFox  | 
 | 12 | +//  | 
 | 13 | +//  Permission is hereby granted, free of charge, to any person obtaining a copy  | 
 | 14 | +//  of this software and associated documentation files (the "Software"), to deal  | 
 | 15 | +//  in the Software without restriction, including without limitation the rights  | 
 | 16 | +//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell  | 
 | 17 | +//  copies of the Software, and to permit persons to whom the Software is  | 
 | 18 | +//  furnished to do so, subject to the following conditions:  | 
 | 19 | +//  | 
 | 20 | +//  The above copyright notice and this permission notice shall be included in all  | 
 | 21 | +//  copies or substantial portions of the Software.  | 
 | 22 | +//  | 
 | 23 | +//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  | 
 | 24 | +//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  | 
 | 25 | +//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE  | 
 | 26 | +//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  | 
 | 27 | +//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  | 
 | 28 | +//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  | 
 | 29 | +//  SOFTWARE.  | 
 | 30 | +//  | 
 | 31 | + | 
 | 32 | +import Foundation  | 
 | 33 | + | 
 | 34 | +public struct JSONPath {  | 
 | 35 | + | 
 | 36 | +    var components: [Component]  | 
 | 37 | + | 
 | 38 | +    enum Component: Equatable {  | 
 | 39 | +        case field(String)  | 
 | 40 | +        case array(Int)  | 
 | 41 | +    }  | 
 | 42 | + | 
 | 43 | +    init(components: [Component]) {  | 
 | 44 | +        self.components = components  | 
 | 45 | +    }  | 
 | 46 | + | 
 | 47 | +    public init(parsing path: String) throws {  | 
 | 48 | +        self.components = try Self.parseComponents(from: path)  | 
 | 49 | +    }  | 
 | 50 | + | 
 | 51 | +    private struct Error: LocalizedError {  | 
 | 52 | +        var errorDescription: String?  | 
 | 53 | + | 
 | 54 | +        init(_ description: String) {  | 
 | 55 | +            self.errorDescription = description  | 
 | 56 | +        }  | 
 | 57 | +    }  | 
 | 58 | +}  | 
 | 59 | + | 
 | 60 | +extension JSONPath {  | 
 | 61 | + | 
 | 62 | +    static func parseComponents(from path: String) throws -> [Component] {  | 
 | 63 | +        var scanner = Scanner(string: path)  | 
 | 64 | +        guard scanner.scanString("$") != nil else {  | 
 | 65 | +            throw Error("Expected $")  | 
 | 66 | +        }  | 
 | 67 | + | 
 | 68 | +        var comps = [Component]()  | 
 | 69 | +        while let comp = try scanComponent(from: &scanner) {  | 
 | 70 | +            comps.append(comp)  | 
 | 71 | +        }  | 
 | 72 | +        return comps  | 
 | 73 | +    }  | 
 | 74 | + | 
 | 75 | +    static func scanComponent(from scanner: inout Scanner) throws -> Component? {  | 
 | 76 | +        if scanner.scanString(".") != nil {  | 
 | 77 | +            guard let name = scanner.scanUpToCharacters(from: CharacterSet(charactersIn: ".[")) else {  | 
 | 78 | +                throw Error("Expected field name")  | 
 | 79 | +            }  | 
 | 80 | +            return .field(name)  | 
 | 81 | +        } else if scanner.scanString("[") != nil {  | 
 | 82 | +            guard let index = scanner.scanCharacters(from: CharacterSet(charactersIn: "0123456789")) else {  | 
 | 83 | +                throw Error("Expected index")  | 
 | 84 | +            }  | 
 | 85 | +            guard scanner.scanString("]") != nil else {  | 
 | 86 | +                throw Error("Expected ]")  | 
 | 87 | +            }  | 
 | 88 | +            return .array(Int(index)!)  | 
 | 89 | +        }  | 
 | 90 | +        guard scanner.isAtEnd else {  | 
 | 91 | +            throw Error("Expected end")  | 
 | 92 | +        }  | 
 | 93 | +        return nil  | 
 | 94 | +    }  | 
 | 95 | +}  | 
0 commit comments