diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..312d1f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output diff --git a/JSONFragmentDecoding.xcodeproj/JSONFragmentDecodingTests_Info.plist b/JSONFragmentDecoding.xcodeproj/JSONFragmentDecodingTests_Info.plist new file mode 100644 index 0000000..7c23420 --- /dev/null +++ b/JSONFragmentDecoding.xcodeproj/JSONFragmentDecodingTests_Info.plist @@ -0,0 +1,25 @@ + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/JSONFragmentDecoding.xcodeproj/JSONFragmentDecoding_Info.plist b/JSONFragmentDecoding.xcodeproj/JSONFragmentDecoding_Info.plist new file mode 100644 index 0000000..57ada9f --- /dev/null +++ b/JSONFragmentDecoding.xcodeproj/JSONFragmentDecoding_Info.plist @@ -0,0 +1,25 @@ + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/JSONFragmentDecoding.xcodeproj/project.pbxproj b/JSONFragmentDecoding.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4e258d1 --- /dev/null +++ b/JSONFragmentDecoding.xcodeproj/project.pbxproj @@ -0,0 +1,537 @@ +// !$*UTF8*$! +{ + archiveVersion = "1"; + objectVersion = "46"; + objects = { + "JSONFragmentDecoding::JSONFragmentDecoding" = { + isa = "PBXNativeTarget"; + buildConfigurationList = "OBJ_18"; + buildPhases = ( + "OBJ_21", + "OBJ_23" + ); + dependencies = ( + ); + name = "JSONFragmentDecoding"; + productName = "JSONFragmentDecoding"; + productReference = "JSONFragmentDecoding::JSONFragmentDecoding::Product"; + productType = "com.apple.product-type.framework"; + }; + "JSONFragmentDecoding::JSONFragmentDecoding::Product" = { + isa = "PBXFileReference"; + path = "JSONFragmentDecoding.framework"; + sourceTree = "BUILT_PRODUCTS_DIR"; + }; + "JSONFragmentDecoding::JSONFragmentDecodingPackageTests::ProductTarget" = { + isa = "PBXAggregateTarget"; + buildConfigurationList = "OBJ_31"; + buildPhases = ( + ); + dependencies = ( + "OBJ_34" + ); + name = "JSONFragmentDecodingPackageTests"; + productName = "JSONFragmentDecodingPackageTests"; + }; + "JSONFragmentDecoding::JSONFragmentDecodingTests" = { + isa = "PBXNativeTarget"; + buildConfigurationList = "OBJ_36"; + buildPhases = ( + "OBJ_39", + "OBJ_42" + ); + dependencies = ( + "OBJ_44" + ); + name = "JSONFragmentDecodingTests"; + productName = "JSONFragmentDecodingTests"; + productReference = "JSONFragmentDecoding::JSONFragmentDecodingTests::Product"; + productType = "com.apple.product-type.bundle.unit-test"; + }; + "JSONFragmentDecoding::JSONFragmentDecodingTests::Product" = { + isa = "PBXFileReference"; + path = "JSONFragmentDecodingTests.xctest"; + sourceTree = "BUILT_PRODUCTS_DIR"; + }; + "JSONFragmentDecoding::SwiftPMPackageDescription" = { + isa = "PBXNativeTarget"; + buildConfigurationList = "OBJ_25"; + buildPhases = ( + "OBJ_28" + ); + dependencies = ( + ); + name = "JSONFragmentDecodingPackageDescription"; + productName = "JSONFragmentDecodingPackageDescription"; + productType = "com.apple.product-type.framework"; + }; + "OBJ_1" = { + isa = "PBXProject"; + attributes = { + LastUpgradeCheck = "9999"; + }; + buildConfigurationList = "OBJ_2"; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = "English"; + hasScannedForEncodings = "0"; + knownRegions = ( + "en" + ); + mainGroup = "OBJ_5"; + productRefGroup = "OBJ_14"; + projectDirPath = "."; + targets = ( + "JSONFragmentDecoding::JSONFragmentDecoding", + "JSONFragmentDecoding::SwiftPMPackageDescription", + "JSONFragmentDecoding::JSONFragmentDecodingPackageTests::ProductTarget", + "JSONFragmentDecoding::JSONFragmentDecodingTests" + ); + }; + "OBJ_10" = { + isa = "PBXGroup"; + children = ( + "OBJ_11" + ); + name = "Tests"; + path = ""; + sourceTree = "SOURCE_ROOT"; + }; + "OBJ_11" = { + isa = "PBXGroup"; + children = ( + "OBJ_12", + "OBJ_13" + ); + name = "JSONFragmentDecodingTests"; + path = "Tests/JSONFragmentDecodingTests"; + sourceTree = "SOURCE_ROOT"; + }; + "OBJ_12" = { + isa = "PBXFileReference"; + path = "JSONFragmentDecodingTests.swift"; + sourceTree = ""; + }; + "OBJ_13" = { + isa = "PBXFileReference"; + path = "XCTestManifests.swift"; + sourceTree = ""; + }; + "OBJ_14" = { + isa = "PBXGroup"; + children = ( + "JSONFragmentDecoding::JSONFragmentDecoding::Product", + "JSONFragmentDecoding::JSONFragmentDecodingTests::Product" + ); + name = "Products"; + path = ""; + sourceTree = "BUILT_PRODUCTS_DIR"; + }; + "OBJ_18" = { + isa = "XCConfigurationList"; + buildConfigurations = ( + "OBJ_19", + "OBJ_20" + ); + defaultConfigurationIsVisible = "0"; + defaultConfigurationName = "Release"; + }; + "OBJ_19" = { + isa = "XCBuildConfiguration"; + buildSettings = { + ENABLE_TESTABILITY = "YES"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks" + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)" + ); + INFOPLIST_FILE = "JSONFragmentDecoding.xcodeproj/JSONFragmentDecoding_Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" + ); + OTHER_CFLAGS = ( + "$(inherited)" + ); + OTHER_LDFLAGS = ( + "$(inherited)" + ); + OTHER_SWIFT_FLAGS = ( + "$(inherited)" + ); + PRODUCT_BUNDLE_IDENTIFIER = "JSONFragmentDecoding"; + PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = "YES"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( + "$(inherited)" + ); + SWIFT_VERSION = "4.2"; + TARGET_NAME = "JSONFragmentDecoding"; + }; + name = "Debug"; + }; + "OBJ_2" = { + isa = "XCConfigurationList"; + buildConfigurations = ( + "OBJ_3", + "OBJ_4" + ); + defaultConfigurationIsVisible = "0"; + defaultConfigurationName = "Release"; + }; + "OBJ_20" = { + isa = "XCBuildConfiguration"; + buildSettings = { + ENABLE_TESTABILITY = "YES"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks" + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)" + ); + INFOPLIST_FILE = "JSONFragmentDecoding.xcodeproj/JSONFragmentDecoding_Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" + ); + OTHER_CFLAGS = ( + "$(inherited)" + ); + OTHER_LDFLAGS = ( + "$(inherited)" + ); + OTHER_SWIFT_FLAGS = ( + "$(inherited)" + ); + PRODUCT_BUNDLE_IDENTIFIER = "JSONFragmentDecoding"; + PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = "YES"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( + "$(inherited)" + ); + SWIFT_VERSION = "4.2"; + TARGET_NAME = "JSONFragmentDecoding"; + }; + name = "Release"; + }; + "OBJ_21" = { + isa = "PBXSourcesBuildPhase"; + files = ( + "OBJ_22" + ); + }; + "OBJ_22" = { + isa = "PBXBuildFile"; + fileRef = "OBJ_9"; + }; + "OBJ_23" = { + isa = "PBXFrameworksBuildPhase"; + files = ( + ); + }; + "OBJ_25" = { + isa = "XCConfigurationList"; + buildConfigurations = ( + "OBJ_26", + "OBJ_27" + ); + defaultConfigurationIsVisible = "0"; + defaultConfigurationName = "Release"; + }; + "OBJ_26" = { + isa = "XCBuildConfiguration"; + buildSettings = { + LD = "/usr/bin/true"; + OTHER_SWIFT_FLAGS = ( + "-swift-version", + "4.2", + "-I", + "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", + "-target", + "x86_64-apple-macosx10.10", + "-sdk", + "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk" + ); + SWIFT_VERSION = "4.2"; + }; + name = "Debug"; + }; + "OBJ_27" = { + isa = "XCBuildConfiguration"; + buildSettings = { + LD = "/usr/bin/true"; + OTHER_SWIFT_FLAGS = ( + "-swift-version", + "4.2", + "-I", + "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", + "-target", + "x86_64-apple-macosx10.10", + "-sdk", + "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk" + ); + SWIFT_VERSION = "4.2"; + }; + name = "Release"; + }; + "OBJ_28" = { + isa = "PBXSourcesBuildPhase"; + files = ( + "OBJ_29" + ); + }; + "OBJ_29" = { + isa = "PBXBuildFile"; + fileRef = "OBJ_6"; + }; + "OBJ_3" = { + isa = "XCBuildConfiguration"; + buildSettings = { + CLANG_ENABLE_OBJC_ARC = "YES"; + COMBINE_HIDPI_IMAGES = "YES"; + COPY_PHASE_STRIP = "NO"; + DEBUG_INFORMATION_FORMAT = "dwarf"; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = "YES"; + GCC_OPTIMIZATION_LEVEL = "0"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)" + ); + MACOSX_DEPLOYMENT_TARGET = "10.10"; + ONLY_ACTIVE_ARCH = "YES"; + OTHER_SWIFT_FLAGS = ( + "-DXcode" + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "macosx"; + SUPPORTED_PLATFORMS = ( + "macosx", + "iphoneos", + "iphonesimulator", + "appletvos", + "appletvsimulator", + "watchos", + "watchsimulator" + ); + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( + "SWIFT_PACKAGE", + "DEBUG" + ); + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + USE_HEADERMAP = "NO"; + }; + name = "Debug"; + }; + "OBJ_31" = { + isa = "XCConfigurationList"; + buildConfigurations = ( + "OBJ_32", + "OBJ_33" + ); + defaultConfigurationIsVisible = "0"; + defaultConfigurationName = "Release"; + }; + "OBJ_32" = { + isa = "XCBuildConfiguration"; + buildSettings = { + }; + name = "Debug"; + }; + "OBJ_33" = { + isa = "XCBuildConfiguration"; + buildSettings = { + }; + name = "Release"; + }; + "OBJ_34" = { + isa = "PBXTargetDependency"; + target = "JSONFragmentDecoding::JSONFragmentDecodingTests"; + }; + "OBJ_36" = { + isa = "XCConfigurationList"; + buildConfigurations = ( + "OBJ_37", + "OBJ_38" + ); + defaultConfigurationIsVisible = "0"; + defaultConfigurationName = "Release"; + }; + "OBJ_37" = { + isa = "XCBuildConfiguration"; + buildSettings = { + CLANG_ENABLE_MODULES = "YES"; + EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks" + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)" + ); + INFOPLIST_FILE = "JSONFragmentDecoding.xcodeproj/JSONFragmentDecodingTests_Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@loader_path/../Frameworks", + "@loader_path/Frameworks" + ); + OTHER_CFLAGS = ( + "$(inherited)" + ); + OTHER_LDFLAGS = ( + "$(inherited)" + ); + OTHER_SWIFT_FLAGS = ( + "$(inherited)" + ); + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( + "$(inherited)" + ); + SWIFT_VERSION = "4.2"; + TARGET_NAME = "JSONFragmentDecodingTests"; + }; + name = "Debug"; + }; + "OBJ_38" = { + isa = "XCBuildConfiguration"; + buildSettings = { + CLANG_ENABLE_MODULES = "YES"; + EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks" + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)" + ); + INFOPLIST_FILE = "JSONFragmentDecoding.xcodeproj/JSONFragmentDecodingTests_Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@loader_path/../Frameworks", + "@loader_path/Frameworks" + ); + OTHER_CFLAGS = ( + "$(inherited)" + ); + OTHER_LDFLAGS = ( + "$(inherited)" + ); + OTHER_SWIFT_FLAGS = ( + "$(inherited)" + ); + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( + "$(inherited)" + ); + SWIFT_VERSION = "4.2"; + TARGET_NAME = "JSONFragmentDecodingTests"; + }; + name = "Release"; + }; + "OBJ_39" = { + isa = "PBXSourcesBuildPhase"; + files = ( + "OBJ_40", + "OBJ_41" + ); + }; + "OBJ_4" = { + isa = "XCBuildConfiguration"; + buildSettings = { + CLANG_ENABLE_OBJC_ARC = "YES"; + COMBINE_HIDPI_IMAGES = "YES"; + COPY_PHASE_STRIP = "YES"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_OPTIMIZATION_LEVEL = "s"; + MACOSX_DEPLOYMENT_TARGET = "10.10"; + OTHER_SWIFT_FLAGS = ( + "-DXcode" + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = "macosx"; + SUPPORTED_PLATFORMS = ( + "macosx", + "iphoneos", + "iphonesimulator", + "appletvos", + "appletvsimulator", + "watchos", + "watchsimulator" + ); + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( + "SWIFT_PACKAGE" + ); + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + USE_HEADERMAP = "NO"; + }; + name = "Release"; + }; + "OBJ_40" = { + isa = "PBXBuildFile"; + fileRef = "OBJ_12"; + }; + "OBJ_41" = { + isa = "PBXBuildFile"; + fileRef = "OBJ_13"; + }; + "OBJ_42" = { + isa = "PBXFrameworksBuildPhase"; + files = ( + "OBJ_43" + ); + }; + "OBJ_43" = { + isa = "PBXBuildFile"; + fileRef = "JSONFragmentDecoding::JSONFragmentDecoding::Product"; + }; + "OBJ_44" = { + isa = "PBXTargetDependency"; + target = "JSONFragmentDecoding::JSONFragmentDecoding"; + }; + "OBJ_5" = { + isa = "PBXGroup"; + children = ( + "OBJ_6", + "OBJ_7", + "OBJ_10", + "OBJ_14" + ); + path = ""; + sourceTree = ""; + }; + "OBJ_6" = { + isa = "PBXFileReference"; + explicitFileType = "sourcecode.swift"; + path = "Package.swift"; + sourceTree = ""; + }; + "OBJ_7" = { + isa = "PBXGroup"; + children = ( + "OBJ_8" + ); + name = "Sources"; + path = ""; + sourceTree = "SOURCE_ROOT"; + }; + "OBJ_8" = { + isa = "PBXGroup"; + children = ( + "OBJ_9" + ); + name = "JSONFragmentDecoding"; + path = "Sources/JSONFragmentDecoding"; + sourceTree = "SOURCE_ROOT"; + }; + "OBJ_9" = { + isa = "PBXFileReference"; + path = "JSONFragmentDecoding.swift"; + sourceTree = ""; + }; + }; + rootObject = "OBJ_1"; +} diff --git a/JSONFragmentDecoding.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/JSONFragmentDecoding.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..fe1aa71 --- /dev/null +++ b/JSONFragmentDecoding.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/JSONFragmentDecoding.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/JSONFragmentDecoding.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/JSONFragmentDecoding.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/JSONFragmentDecoding.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/JSONFragmentDecoding.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..a72dc2b --- /dev/null +++ b/JSONFragmentDecoding.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + + + \ No newline at end of file diff --git a/JSONFragmentDecoding.xcodeproj/xcshareddata/xcschemes/JSONFragmentDecoding-Package.xcscheme b/JSONFragmentDecoding.xcodeproj/xcshareddata/xcschemes/JSONFragmentDecoding-Package.xcscheme new file mode 100644 index 0000000..1d9df0a --- /dev/null +++ b/JSONFragmentDecoding.xcodeproj/xcshareddata/xcschemes/JSONFragmentDecoding-Package.xcscheme @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JSONFragmentDecoding.xcodeproj/xcshareddata/xcschemes/JSONFragmentDecoding.xcscheme b/JSONFragmentDecoding.xcodeproj/xcshareddata/xcschemes/JSONFragmentDecoding.xcscheme new file mode 100644 index 0000000..938e4a8 --- /dev/null +++ b/JSONFragmentDecoding.xcodeproj/xcshareddata/xcschemes/JSONFragmentDecoding.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/JSONFragmentDecoding.xcodeproj/xcshareddata/xcschemes/JSONFragmentDecodingTests.xcscheme b/JSONFragmentDecoding.xcodeproj/xcshareddata/xcschemes/JSONFragmentDecodingTests.xcscheme new file mode 100644 index 0000000..2b878a6 --- /dev/null +++ b/JSONFragmentDecoding.xcodeproj/xcshareddata/xcschemes/JSONFragmentDecodingTests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a0f93b7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Hamish Knight + +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. diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..30113a2 --- /dev/null +++ b/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version:4.0 + +import PackageDescription + +let package = Package( + name: "JSONFragmentDecoding", + products: [ + .library( + name: "JSONFragmentDecoding", + targets: ["JSONFragmentDecoding"] + ), + ], + dependencies: [ + + ], + targets: [ + .target( + name: "JSONFragmentDecoding", + dependencies: [] + ), + .testTarget( + name: "JSONFragmentDecodingTests", + dependencies: ["JSONFragmentDecoding"] + ), + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c714887 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# JSONFragmentDecoding +A JSONDecoder extension to allow decoding JSON fragments diff --git a/Sources/JSONFragmentDecoding/JSONFragmentDecoding.swift b/Sources/JSONFragmentDecoding/JSONFragmentDecoding.swift new file mode 100644 index 0000000..0a0d3bc --- /dev/null +++ b/Sources/JSONFragmentDecoding/JSONFragmentDecoding.swift @@ -0,0 +1,78 @@ + +import Foundation + +fileprivate extension CodingUserInfoKey { + static let fragmentBoxedType = CodingUserInfoKey( + rawValue: "CodingUserInfoKey.fragmentBoxedType" + )! +} + +fileprivate extension Decodable { + static func __decode( + from container: inout UnkeyedDecodingContainer + ) throws -> Self { + return try container.decode(self) + } +} + +extension JSONDecoder { + private struct FragmentDecodingBox : Decodable { + var value: T + init(from decoder: Decoder) throws { + let type = decoder.userInfo[.fragmentBoxedType] as! T.Type + var container = try decoder.unkeyedContainer() + self.value = try type.__decode(from: &container) + } + } + + private func copy() -> JSONDecoder { + let decoder = JSONDecoder() + decoder.dataDecodingStrategy = dataDecodingStrategy + decoder.dateDecodingStrategy = dateDecodingStrategy + decoder.keyDecodingStrategy = keyDecodingStrategy + decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy + decoder.userInfo = userInfo + return decoder + } + + public func decode( + _ type: T.Type, from data: Data, allowFragments: Bool + ) throws -> T { + // If we're not allowing fragments, just delegate to decode(_:from:). + guard allowFragments else { return try decode(type, from: data) } + + // Box the JSON object in an array so we can pass it off to JSONDecoder. + // The round-tripping through JSONSerialization isn't ideal, but it + // ensures we do The Right Thing regardless of the encoding of `data`. + let jsonObject = try JSONSerialization + .jsonObject(with: data, options: .allowFragments) + let boxedData = try JSONSerialization.data(withJSONObject: [jsonObject]) + + // Copy the decoder so we can mutate the userInfo without having to worry + // about data races. + let decoder = copy() + decoder.userInfo[.fragmentBoxedType] = type + + // Use FragmentDecodingBox to decode the underlying fragment from the + // array. + // + // We're intentionally *not* doing `decode([T].self, ...)` here, as + // that loses the dynamic type passed – breaking things like: + // + // class C : Decodable {} + // class D {} + // + // let type: C.Type = D.self + // let data = ... + // let decoded = try JSONDecoder().decode(type, from: data, allowFragments: true) + // + // The above would decode a `C` instead of a `D` if we didn't preserve + // the dynamic type. + // + // (Admittedly this is a bit of contrived example, as by default such types + // would decode using keyed containers and therefore not be fragments – + // nontheless it is possible for them to implement their decoding such that + // they use a single value container). + return try decoder.decode(FragmentDecodingBox.self, from: boxedData).value + } +} diff --git a/Tests/JSONFragmentDecodingTests/JSONFragmentDecodingTests.swift b/Tests/JSONFragmentDecodingTests/JSONFragmentDecodingTests.swift new file mode 100644 index 0000000..32a6025 --- /dev/null +++ b/Tests/JSONFragmentDecodingTests/JSONFragmentDecodingTests.swift @@ -0,0 +1,68 @@ + +import XCTest +@testable import JSONFragmentDecoding + +func XCTAssertEqual( + _ expected: Any.Type, _ actual: Any.Type, + file: StaticString = #file, line: UInt = #line) { + XCTAssert( + expected == actual, "incorrect type \(actual), expected \(expected)", + file: file, line: line + ) +} + +final class JSONFragmentDecodingTests : XCTestCase { + + func testFragmentDecoding() throws { + + XCTAssertEqual(10, try JSONDecoder() + .decode(Int.self, from: Data("10".utf8), allowFragments: true)) + + XCTAssertEqual("10", try JSONDecoder() + .decode(String.self, from: Data("\"10\"".utf8), allowFragments: true)) + } + + func testExoticFragmentDecoding() throws { + + class C : Decodable { + var i: Int + required init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + self.i = try container.decode(Int.self) + } + } + class D : C {} + + let metatype: C.Type = D.self + let data = "10".data(using: .utf32BigEndian)! + let decoded = try JSONDecoder() + .decode(metatype, from: data, allowFragments: true) + + XCTAssertEqual(D.self, type(of: decoded)) + XCTAssertEqual(10, decoded.i) + } + + func testFloatStrategyDecoding() throws { + let decoder = JSONDecoder() + decoder.nonConformingFloatDecodingStrategy = .convertFromString( + positiveInfinity: "inf", negativeInfinity: "-inf", nan: "nan" + ) + + let decodedInf = try decoder + .decode(Double.self, from: Data("\"inf\"".utf8), allowFragments: true) + XCTAssertEqual(.infinity, decodedInf) + + let decodedNegInf = try decoder + .decode(Double.self, from: Data("\"-inf\"".utf8), allowFragments: true) + XCTAssertEqual(-.infinity, decodedNegInf) + + let decodedNan = try decoder + .decode(Double.self, from: Data("\"nan\"".utf8), allowFragments: true) + XCTAssert(decodedNan.isNaN) + } + + static var allTests = [ + ("testFragmentDecoding", testFragmentDecoding), + ("testExoticFragmentDecoding", testExoticFragmentDecoding) + ] +} diff --git a/Tests/JSONFragmentDecodingTests/XCTestManifests.swift b/Tests/JSONFragmentDecodingTests/XCTestManifests.swift new file mode 100644 index 0000000..9b4199c --- /dev/null +++ b/Tests/JSONFragmentDecodingTests/XCTestManifests.swift @@ -0,0 +1,9 @@ +import XCTest + +#if !os(macOS) +public func allTests() -> [XCTestCaseEntry] { + return [ + testCase(JSONFragmentDecodingTests.allTests), + ] +} +#endif diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift new file mode 100644 index 0000000..f237bbb --- /dev/null +++ b/Tests/LinuxMain.swift @@ -0,0 +1,7 @@ +import XCTest + +import JSONFragmentDecodingTests + +var tests = [XCTestCaseEntry]() +tests += JSONFragmentDecodingTests.allTests() +XCTMain(tests) \ No newline at end of file