diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..ba72e725 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + build-and-test: + runs-on: macos-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build + run: xcrun swift build --build-tests + + - name: Run tests + run: xcrun swift test \ No newline at end of file diff --git a/.swiftlint.yml b/.swiftlint.yml index 03003d1d..36ee05ac 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,7 +1,7 @@ disabled_rules: - force_cast - force_try - - variable_name + - identifier_name - type_name - file_length - line_length diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 17140ab1..00000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: objective-c -osx_image: xcode10.2 -xcode_sdk: iphonesimulator12.0 -script: -- set -o pipefail -- travis_retry xcodebuild -workspace SwiftyJSON.xcworkspace -scheme "SwiftyJSON iOS" -destination "platform=iOS Simulator,name=iPhone 6" build-for-testing test | xcpretty -- travis_retry xcodebuild -workspace SwiftyJSON.xcworkspace -scheme "SwiftyJSON macOS" build-for-testing test | xcpretty -- travis_retry xcodebuild -workspace SwiftyJSON.xcworkspace -scheme "SwiftyJSON tvOS" -destination "platform=tvOS Simulator,name=Apple TV" build-for-testing test | xcpretty diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a0935d3..082b0247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ [Full Changelog](https://github.com/SwiftyJSON/SwiftyJSON/compare/2.2.0...HEAD) +### Added +- Swift 6 support with full Sendable conformance ([#1163](https://github.com/SwiftyJSON/SwiftyJSON/issues/1163)) +- Comprehensive concurrency tests for actor boundary validation + +### Changed +- `JSON` struct conforms to `@unchecked Sendable` +- `Type`, `SwiftyJSONError`, `JSONKey` enums conform to `Sendable` +- Minimum Swift tools version updated to 6.0 + **Closed issues:** - 156 compiler errors Mavericks + Xcode 6.2 [\#220](https://github.com/SwiftyJSON/SwiftyJSON/issues/220) diff --git a/Package.swift b/Package.swift index 744e95dd..fb026e55 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:6.0 import PackageDescription let package = Package( @@ -7,8 +7,18 @@ let package = Package( .library(name: "SwiftyJSON", targets: ["SwiftyJSON"]) ], targets: [ - .target(name: "SwiftyJSON", dependencies: []), - .testTarget(name: "SwiftJSONTests", dependencies: ["SwiftyJSON"]) - ], - swiftLanguageVersions: [.v5] + .target(name: "SwiftyJSON", + dependencies: [], + resources: [ + .copy("PrivacyInfo.xcprivacy") + ] + ), + .testTarget( + name: "SwiftJSONTests", + dependencies: ["SwiftyJSON"], + resources: [ + .copy("Tests.json") + ] + ) + ] ) diff --git a/README.md b/README.md index a4cd321f..a6f3037a 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,11 @@ # SwiftyJSON +[![CI](https://github.com/SwiftyJSON/SwiftyJSON/actions/workflows/ci.yml/badge.svg)](https://github.com/SwiftyJSON/SwiftyJSON/actions/workflows/ci.yml) + [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) ![CocoaPods](https://img.shields.io/cocoapods/v/SwiftyJSON.svg) ![Platform](https://img.shields.io/badge/platforms-iOS%208.0%20%7C%20macOS%2010.10%20%7C%20tvOS%209.0%20%7C%20watchOS%203.0-F28D00.svg) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) SwiftyJSON makes it easy to deal with JSON data in Swift. -Platform | Build Status ----------| --------------| -*OS | [![Travis CI](https://travis-ci.org/SwiftyJSON/SwiftyJSON.svg?branch=master)](https://travis-ci.org/SwiftyJSON/SwiftyJSON) | -[Linux](https://github.com/IBM-Swift/SwiftyJSON) | [![Build Status](https://travis-ci.org/IBM-Swift/SwiftyJSON.svg?branch=master)](https://travis-ci.org/IBM-Swift/SwiftyJSON) | - - 1. [Why is the typical JSON handling in Swift NOT good](#why-is-the-typical-json-handling-in-swift-not-good) 2. [Requirements](#requirements) 3. [Integration](#integration) @@ -24,6 +20,7 @@ Platform | Build Status - [Raw object](#raw-object) - [Literal convertibles](#literal-convertibles) - [Merging](#merging) + - [Removing elements](#removing-elements) 5. [Work with Alamofire](#work-with-alamofire) 6. [Work with Moya](#work-with-moya) 7. [SwiftyJSON Model Generator](#swiftyjson-model-generator) @@ -61,7 +58,7 @@ An unreadable mess--for something that should really be simple! With SwiftyJSON all you have to do is: ```swift -let json = JSON(data: dataFromNetworking) +let json = try? JSON(data: dataFromNetworking) if let userName = json[0]["user"]["name"].string { //Now you got your value } @@ -70,7 +67,7 @@ if let userName = json[0]["user"]["name"].string { And don't worry about the Optional Wrapping thing. It's done for you automatically. ```swift -let json = JSON(data: dataFromNetworking) +let json = try? JSON(data: dataFromNetworking) let result = json[999999]["wrong_key"]["wrong_name"] if let userName = result.string { //Calm down, take it easy, the ".string" property still produces the correct Optional String type with safety @@ -83,7 +80,8 @@ if let userName = result.string { ## Requirements - iOS 8.0+ | macOS 10.10+ | tvOS 9.0+ | watchOS 2.0+ -- Xcode 8 +- Xcode 16+ +- Swift 6.0+ ## Integration @@ -143,7 +141,7 @@ import SwiftyJSON ``` ```swift -let json = JSON(data: dataFromNetworking) +let json = try? JSON(data: dataFromNetworking) ``` Or @@ -503,6 +501,69 @@ let updated = original.merge(with: update) // ] ``` + +#### Removing elements + +If you are storing dictionaries, you can remove elements using `dictionaryObject.removeValue(forKey:)`. This mutates the JSON object in place. + +For example: + +```swift +var object = JSON([ + "one": ["color": "blue"], + "two": ["city": "tokyo", + "country": "japan", + "foods": [ + "breakfast": "tea", + "lunch": "sushi" + ] + ] +]) +``` + +Lets remove the `country` key: + +```swift +object["two"].dictionaryObject?.removeValue(forKey: "country") +``` + +If you `print(object)`, you'll see that the `country` key no longer exists. + +```json +{ + "one" : { + "color" : "blue" + }, + "two" : { + "city" : "tokyo", + "foods" : { + "breakfast" : "tea", + "lunch" : "sushi" + } + } +} +``` + +This also works for nested dictionaries: + +```swift +object["two"]["foods"].dictionaryObject?.removeValue(forKey: "breakfast") +``` + +```json +{ + "one" : { + "color" : "blue" + }, + "two" : { + "city" : "tokyo", + "foods" : { + "lunch" : "sushi" + } + } +} +``` + ## String representation There are two options available: - use the default Swift one @@ -556,5 +617,4 @@ provider.request(.showProducts) { result in ## SwiftyJSON Model Generator Tools to generate SwiftyJSON Models -* [JSON Cafe](http://www.jsoncafe.com/) * [JSON Export](https://github.com/Ahmed-Ali/JSONExport) diff --git a/Source/SwiftyJSON/PrivacyInfo.xcprivacy b/Source/SwiftyJSON/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..d37d6275 --- /dev/null +++ b/Source/SwiftyJSON/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + + diff --git a/Source/SwiftyJSON/SwiftyJSON.swift b/Source/SwiftyJSON/SwiftyJSON.swift index e625810e..d2f39ef2 100644 --- a/Source/SwiftyJSON/SwiftyJSON.swift +++ b/Source/SwiftyJSON/SwiftyJSON.swift @@ -24,7 +24,7 @@ import Foundation // MARK: - Error // swiftlint:disable line_length -public enum SwiftyJSONError: Int, Swift.Error { +public enum SwiftyJSONError: Int, Swift.Error, Sendable { case unsupportedType = 999 case indexOutOfBounds = 900 case elementTooDeep = 902 @@ -67,7 +67,7 @@ JSON's type definitions. See http://www.json.org */ -public enum Type: Int { +public enum Type: Int, Sendable { case number case string case bool @@ -79,7 +79,7 @@ public enum Type: Int { // MARK: - JSON Base -public struct JSON { +public struct JSON: @unchecked Sendable { /** Creates a JSON using the data. @@ -265,11 +265,7 @@ private func unwrap(_ object: Any) -> Any { case let array as [Any]: return array.map(unwrap) case let dictionary as [String: Any]: - var d = dictionary - dictionary.forEach { pair in - d[pair.key] = unwrap(pair.value) - } - return d + return dictionary.mapValues(unwrap) default: return object } @@ -343,7 +339,7 @@ extension JSON: Swift.Collection { /** * To mark both String and Int can be used in subscript. */ -public enum JSONKey { +public enum JSONKey: Sendable { case index(Int) case key(String) } @@ -433,7 +429,7 @@ extension JSON { Example: - ``` + ```swift let json = JSON[data] let path = [9,"list","person","name"] let name = json[path] @@ -467,10 +463,14 @@ extension JSON { Find a json in the complex data structures by using array of Int and/or String as path. - parameter path: The target json's path. Example: - + ```swift let name = json[9,"list","person","name"] - - The same as: let name = json[9]["list"]["person"]["name"] + ``` + + The same as: + ```swift + let name = json[9]["list"]["person"]["name"] + ``` - returns: Return a json found by the path or a null json with error */ @@ -524,7 +524,7 @@ extension JSON: Swift.ExpressibleByFloatLiteral { extension JSON: Swift.ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (String, Any)...) { - let dictionary = elements.reduce(into: [String: Any](), { $0[$1.0] = $1.1}) + let dictionary = Dictionary(elements, uniquingKeysWith: { $1 }) self.init(dictionary) } } @@ -676,7 +676,7 @@ extension JSON { //Optional [JSON] public var array: [JSON]? { - return type == .array ? rawArray.map { JSON($0) } : nil + return type == .array ? rawArray.map(JSON.init(_:)) : nil } //Non-optional [JSON] @@ -705,11 +705,7 @@ extension JSON { //Optional [String : JSON] public var dictionary: [String: JSON]? { if type == .dictionary { - var d = [String: JSON](minimumCapacity: rawDictionary.count) - rawDictionary.forEach { pair in - d[pair.key] = JSON(pair.value) - } - return d + return rawDictionary.mapValues(JSON.init(_:)) } else { return nil } @@ -791,7 +787,7 @@ extension JSON { switch type { case .string: return object as? String ?? "" case .number: return rawNumber.stringValue - case .bool: return (object as? Bool).map { String($0) } ?? "" + case .bool: return (object as? Bool).map(String.init) ?? "" default: return "" } } diff --git a/SwiftyJSON.podspec b/SwiftyJSON.podspec index 944a24b7..dd091231 100644 --- a/SwiftyJSON.podspec +++ b/SwiftyJSON.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SwiftyJSON" - s.version = "5.0.1" + s.version = "5.0.2" s.summary = "SwiftyJSON makes it easy to deal with JSON data in Swift" s.homepage = "https://github.com/SwiftyJSON/SwiftyJSON" s.license = { :type => "MIT" } @@ -14,4 +14,5 @@ Pod::Spec.new do |s| s.tvos.deployment_target = "9.0" s.source = { :git => "https://github.com/SwiftyJSON/SwiftyJSON.git", :tag => s.version } s.source_files = "Source/SwiftyJSON/*.swift" + s.resource_bundles = {'SwiftyJSON' => ['Source/SwiftyJSON/PrivacyInfo.xcprivacy']} end diff --git a/SwiftyJSON.xcodeproj/project.pbxproj b/SwiftyJSON.xcodeproj/project.pbxproj index 3f48deaa..3c325ac2 100644 --- a/SwiftyJSON.xcodeproj/project.pbxproj +++ b/SwiftyJSON.xcodeproj/project.pbxproj @@ -51,6 +51,10 @@ 9C459F041A9103C1008C9A41 /* DictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B66C8B19E51D6500540692 /* DictionaryTests.swift */; }; 9C459F051A9103C1008C9A41 /* ArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B66C8D19E52F4200540692 /* ArrayTests.swift */; }; 9C7DFC661A9102BD005AA3F7 /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C7DFC5B1A9102BD005AA3F7 /* SwiftyJSON.framework */; }; + A1DE64C62BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A1DE64C52BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy */; }; + A1DE64C72BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A1DE64C52BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy */; }; + A1DE64C82BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A1DE64C52BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy */; }; + A1DE64C92BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A1DE64C52BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy */; }; A819C49719E1A7DD00ADCC3D /* LiteralConvertibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A819C49619E1A7DD00ADCC3D /* LiteralConvertibleTests.swift */; }; A819C49919E1B10300ADCC3D /* RawRepresentableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A819C49819E1B10300ADCC3D /* RawRepresentableTests.swift */; }; A819C49F19E2EE5B00ADCC3D /* SubscriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A819C49E19E2EE5B00ADCC3D /* SubscriptTests.swift */; }; @@ -126,6 +130,7 @@ 9C459EF61A9103B1008C9A41 /* Info-macOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Info-macOS.plist"; path = "../Info-macOS.plist"; sourceTree = ""; }; 9C7DFC5B1A9102BD005AA3F7 /* SwiftyJSON.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyJSON.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9C7DFC651A9102BD005AA3F7 /* SwiftyJSON macOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftyJSON macOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + A1DE64C52BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = SwiftyJSON/PrivacyInfo.xcprivacy; sourceTree = ""; }; A819C49619E1A7DD00ADCC3D /* LiteralConvertibleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LiteralConvertibleTests.swift; path = ../SwiftJSONTests/LiteralConvertibleTests.swift; sourceTree = ""; }; A819C49819E1B10300ADCC3D /* RawRepresentableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RawRepresentableTests.swift; path = ../SwiftJSONTests/RawRepresentableTests.swift; sourceTree = ""; }; A819C49E19E2EE5B00ADCC3D /* SubscriptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SubscriptTests.swift; path = ../SwiftJSONTests/SubscriptTests.swift; sourceTree = ""; }; @@ -241,6 +246,7 @@ 2E4FEFDE19575BE100351305 /* Supporting Files */ = { isa = PBXGroup; children = ( + A1DE64C52BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy */, 2E4FEFDF19575BE100351305 /* Info-iOS.plist */, 030B6CDC1A6E171D00C2D4F1 /* Info-macOS.plist */, E4D7CCE91B9465A800EE7221 /* Info-watchOS.plist */, @@ -272,7 +278,7 @@ 2E4FEFEB19575BE100351305 /* Supporting Files */, ); name = Tests; - path = Tests/Tes; + path = Tests/SwiftJSONTests; sourceTree = ""; }; 2E4FEFEB19575BE100351305 /* Supporting Files */ = { @@ -527,6 +533,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1DE64C62BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -542,6 +549,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1DE64C92BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -549,6 +557,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1DE64C72BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -572,6 +581,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1DE64C82BC7D95C0097BCE6 /* PrivacyInfo.xcprivacy in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/SwiftJSONTests/BaseTests.swift b/Tests/SwiftJSONTests/BaseTests.swift index 9139c204..8bbebcde 100644 --- a/Tests/SwiftJSONTests/BaseTests.swift +++ b/Tests/SwiftJSONTests/BaseTests.swift @@ -31,10 +31,13 @@ class BaseTests: XCTestCase { super.setUp() -// let file = "./Tests/Tes/Tests.json" -// self.testData = try? Data(contentsOf: URL(fileURLWithPath: file)) - if let file = Bundle(for: BaseTests.self).path(forResource: "Tests", ofType: "json") { - self.testData = try? Data(contentsOf: URL(fileURLWithPath: file)) + #if SWIFT_PACKAGE + let testBundle = Bundle.module + #else + let testBundle = Bundle(for: BaseTests.self) + #endif + if let file = testBundle.url(forResource: "Tests", withExtension: "json") { + self.testData = try? Data(contentsOf: file) } else { XCTFail("Can't find the test JSON file") } diff --git a/Tests/SwiftJSONTests/ConcurrencyTests.swift b/Tests/SwiftJSONTests/ConcurrencyTests.swift new file mode 100644 index 00000000..a6eaa099 --- /dev/null +++ b/Tests/SwiftJSONTests/ConcurrencyTests.swift @@ -0,0 +1,89 @@ +// ConcurrencyTests.swift +// +// Copyright (c) 2014 - 2017 Ruoyu Fu, Pinglin Tang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import XCTest +import SwiftyJSON + +/// Tests verifying Sendable conformance for Swift 6 concurrency. +/// Each test targets a specific type made Sendable in this PR. +class ConcurrencyTests: XCTestCase { + + actor Processor { + func extract(_ json: JSON) -> String { + json["name"].stringValue + } + + func extractType(_ json: JSON) -> Type { + json["value"].type + } + + func requireField(_ json: JSON) throws -> String { + guard json["required"].exists() else { + throw SwiftyJSONError.notExist + } + return json["required"].stringValue + } + } + + /// Verifies JSON conforms to Sendable (can cross actor boundary) + func testJSONSendable() async { + let json = JSON(["name": "test", "count": 42]) + let processor = Processor() + + let result = await processor.extract(json) + + XCTAssertEqual(result, "test") + } + + /// Verifies Type enum conforms to Sendable (can be returned across actor boundary) + func testTypeSendable() async { + let processor = Processor() + + let stringType = await processor.extractType(JSON(["value": "hello"])) + let numberType = await processor.extractType(JSON(["value": 123])) + let boolType = await processor.extractType(JSON(["value": true])) + let nullType = await processor.extractType(JSON(["value": NSNull()])) + let arrayType = await processor.extractType(JSON(["value": [1, 2, 3]])) + let dictType = await processor.extractType(JSON(["value": ["nested": "object"]])) + + XCTAssertEqual(stringType, .string) + XCTAssertEqual(numberType, .number) + XCTAssertEqual(boolType, .bool) + XCTAssertEqual(nullType, .null) + XCTAssertEqual(arrayType, .array) + XCTAssertEqual(dictType, .dictionary) + } + + /// Verifies SwiftyJSONError conforms to Sendable (can be thrown across actor boundary) + func testSwiftyJSONErrorSendable() async { + let processor = Processor() + + do { + _ = try await processor.requireField(JSON(["other": "value"])) + XCTFail("Should have thrown") + } catch SwiftyJSONError.notExist { + // Error successfully crossed actor boundary + } catch { + XCTFail("Unexpected error: \(error)") + } + } +} diff --git a/Tests/SwiftJSONTests/PerformanceTests.swift b/Tests/SwiftJSONTests/PerformanceTests.swift index 7535f7d8..c0e6cc0b 100644 --- a/Tests/SwiftJSONTests/PerformanceTests.swift +++ b/Tests/SwiftJSONTests/PerformanceTests.swift @@ -30,8 +30,13 @@ class PerformanceTests: XCTestCase { override func setUp() { super.setUp() - if let file = Bundle(for: PerformanceTests.self).path(forResource: "Tests", ofType: "json") { - self.testData = try? Data(contentsOf: URL(fileURLWithPath: file)) + #if SWIFT_PACKAGE + let testBundle = Bundle.module + #else + let testBundle = Bundle(for: PerformanceTests.self) + #endif + if let file = testBundle.url(forResource: "Tests", withExtension: "json") { + self.testData = try? Data(contentsOf: file) } else { XCTFail("Can't find the test JSON file") } diff --git a/Tests/SwiftJSONTests/SequenceTypeTests.swift b/Tests/SwiftJSONTests/SequenceTypeTests.swift index d0d8cadd..a039453f 100644 --- a/Tests/SwiftJSONTests/SequenceTypeTests.swift +++ b/Tests/SwiftJSONTests/SequenceTypeTests.swift @@ -25,10 +25,17 @@ import SwiftyJSON class SequenceTypeTests: XCTestCase { + var testData: Data? + func testJSONFile() { - if let file = Bundle(for: BaseTests.self).path(forResource: "Tests", ofType: "json") { - let testData = try? Data(contentsOf: URL(fileURLWithPath: file)) - guard let json = try? JSON(data: testData!) else { + #if SWIFT_PACKAGE + let testBundle = Bundle.module + #else + let testBundle = Bundle(for: SequenceTypeTests.self) + #endif + if let file = testBundle.url(forResource: "Tests", withExtension: "json") { + self.testData = try? Data(contentsOf: file) + guard let json = try? JSON(data: self.testData!) else { XCTFail("Unable to parse the data") return } diff --git a/Tests/Tes/Tests.json b/Tests/SwiftJSONTests/Tests.json similarity index 100% rename from Tests/Tes/Tests.json rename to Tests/SwiftJSONTests/Tests.json