├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── DevGuideline.md ├── KakaJSON.podspec ├── KakaJSON.xcodeproj ├── KakaJSONTests_Info.plist ├── KakaJSON_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── KakaJSON-Package.xcscheme ├── KakaJSON.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── KakaJSONDemo ├── KakaJSONDemo.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── KakaJSONDemo.xcscheme └── KakaJSONDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── KakaJSON │ ├── Convert │ ├── Convertible.swift │ ├── ConvertibleConfig.swift │ ├── ConvertibleEnum.swift │ ├── KJ.swift │ ├── Logger.swift │ ├── TypeProxy.swift │ └── Values.swift │ ├── Extension │ ├── Array+KJ.swift │ ├── Data+KJ.swift │ ├── Dictionary+KJ.swift │ ├── JSONSerialization+KJ.swift │ ├── NSObject+KJ.swift │ ├── Optional+KJ.swift │ ├── Pointer+KJ.swift │ └── String+KJ.swift │ ├── Global │ ├── Coding.swift │ ├── JSON.swift │ └── Model.swift │ └── Metadata │ ├── Descriptor │ ├── ClassDescriptor.swift │ ├── Descriptor.swift │ ├── EnumDescriptor.swift │ ├── FieldDescriptor.swift │ └── StructDescriptor.swift │ ├── LICENSE │ ├── Layout │ ├── BaseLayout.swift │ ├── ClassLayout.swift │ ├── EnumLayout.swift │ ├── FieldList.swift │ ├── FunctionLayout.swift │ ├── Layout.swift │ ├── ProtocolLayout.swift │ ├── StructLayout.swift │ └── TupleLayout.swift │ ├── Metadata.swift │ ├── Property.swift │ └── Type │ ├── BaseType.swift │ ├── ClassType.swift │ ├── EnumType.swift │ ├── ForeignClassType.swift │ ├── FunctionType.swift │ ├── Kind.swift │ ├── MetaType.swift │ ├── ModelType.swift │ ├── ObjCClassType.swift │ ├── OptionalType.swift │ ├── ProtocolType.swift │ ├── StructType.swift │ ├── TupleType.swift │ └── Type.swift └── Tests ├── KakaJSONTests ├── JSON_To_Model │ ├── JTM_01_Basic.swift │ ├── JTM_02_DataType.swift │ ├── JTM_03_NestedModel.swift │ ├── JTM_04_ModelArray.swift │ ├── JTM_05_KeyMapping.swift │ ├── JTM_06_CustomValue.swift │ └── JTM_07_DynamicModel.swift ├── Model_To_JSON │ ├── MTJ_01_Basic.swift │ ├── MTJ_02_NestedModel.swift │ ├── MTJ_03_ModelArray.swift │ ├── MTJ_04_KeyMapping.swift │ └── MTJ_05_CustomValue.swift ├── OC │ ├── KakaJSONTests-Bridging-Header.h │ ├── OCModel.h │ └── OCModel.m ├── Other │ ├── Bugs.swift │ ├── Coding.swift │ └── Global.swift └── XCTestManifests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - PROJECT="KakaJSON.xcodeproj" 4 | - SCHEME="KakaJSON-Package" 5 | - SWIFT_VERSION=5.0 6 | 7 | matrix: 8 | include: 9 | - os: osx 10 | language: swift 11 | osx_image: xcode10.2 12 | env: 13 | - SDK="iphonesimulator12.2" 14 | - DESTINATION="platform=iOS Simulator,name=iPhone 8,OS=12.2" 15 | - os: osx 16 | language: swift 17 | osx_image: xcode10.2 18 | env: 19 | - SDK="macosx10.14" 20 | - DESTINATION="arch=x86_64" 21 | - os: osx 22 | language: swift 23 | osx_image: xcode10.2 24 | env: 25 | - SDK="appletvsimulator12.0" 26 | - DESTINATION="OS=12.0,name=Apple TV 4K" 27 | # - os: linux 28 | # language: generic 29 | # dist: trusty 30 | # sudo: required 31 | 32 | before_install: 33 | - echo $TRAVIS_OS_NAME 34 | - if [[ $TRAVIS_OS_NAME == 'osx' ]]; then 35 | gem install xcpretty; 36 | fi 37 | - if [[ $TRAVIS_OS_NAME == 'linux' ]]; then 38 | eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"; 39 | fi 40 | 41 | script: 42 | - if [[ $TRAVIS_OS_NAME == 'osx' ]]; then 43 | xcodebuild clean build test -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -enableCodeCoverage YES | xcpretty; 44 | fi 45 | - if [[ $TRAVIS_OS_NAME == 'linux' ]]; then 46 | swift test; 47 | fi -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | --- 4 | 5 | ## [1.1.2](https://github.com/kakaopensource/KakaJSON/releases/tag/1.1.2) (2019-12-23) 6 | - Supporting convert from [String: [[String: Any]]] to [String: [Model]] 7 | - Supporting 'enum array', 'enum dict' 8 | - Closed issues 9 | - [嵌套解析出错](https://github.com/kakaopensource/KakaJSON/issues/39) 10 | 11 | ## [1.1.1](https://github.com/kakaopensource/KakaJSON/releases/tag/1.1.1) (2019-11-04) 12 | - Adjust Lock 13 | - Closed issues 14 | - [iOS 9 Crash](https://github.com/kakaopensource/KakaJSON/issues/31) 15 | - Merged pull requests 16 | - [use isUppercase to check if the character is uppercase](https://github.com/kakaopensource/KakaJSON/pull/20) 17 | - [A key should be able to contain dot](https://github.com/kakaopensource/KakaJSON/pull/21) 18 | - [use enum as namespace, as Apple does in Combine](https://github.com/kakaopensource/KakaJSON/pull/23) 19 | - [return early if possible](https://github.com/kakaopensource/KakaJSON/pull/24) 20 | - [add test testComplex_JSONKeyWithDot_bestMatch](https://github.com/kakaopensource/KakaJSON/pull/25) 21 | - [[big pr]: refactor project structure](https://github.com/kakaopensource/KakaJSON/pull/27) 22 | 23 | 24 | 25 | ## [1.1.0](https://github.com/kakaopensource/KakaJSON/releases/tag/1.1.0) (2019-08-28) 26 | - Remove locks in ConvertibleConfig 27 | - Simplify apis 28 | - Support carthage 29 | - Keep default model value for optimization 30 | - Merged pull requests 31 | - [simplify optional.kj_value](https://github.com/kakaopensource/KakaJSON/pull/16) 32 | - [replace symbol ~= with pattern keyword](https://github.com/kakaopensource/KakaJSON/pull/17) 33 | 34 | 35 | 36 | ## [1.0.0](https://github.com/kakaopensource/KakaJSON/releases/tag/1.0.0) (2019-08-23) 37 | 38 | - First public release 39 | -------------------------------------------------------------------------------- /DevGuideline.md: -------------------------------------------------------------------------------- 1 | 2 | ## KakaJSON DevGuideline 3 | 4 | #### Metadata Reference: 5 | 6 | - [https://github.com/apple/swift/blob/master/docs/ABI/TypeMetadata.rst](https://github.com/apple/swift/blob/master/docs/ABI/TypeMetadata.rst) 7 | - [https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataKind.def](https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataKind.def) 8 | - [https://github.com/apple/swift/blob/master/include/swift/ABI/Metadata.h](https://github.com/apple/swift/blob/master/include/swift/ABI/Metadata.h) 9 | - [https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataValues.h](https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataValues.h) 10 | - [https://github.com/apple/swift/blob/master/utils/dtrace/runtime.d](https://github.com/apple/swift/blob/master/utils/dtrace/runtime.d) 11 | - [https://github.com/apple/swift/blob/master/include/swift/Reflection/Records.h](https://github.com/apple/swift/blob/master/include/swift/Reflection/Records.h) 12 | 13 | #### 通过测试用例 14 | 15 | 提交代码之前,请务必先保证在真机、模拟器上通过所有的测试用例(Debug+Release模式) 16 | 17 | 18 | #### 命名规范 19 | 20 | ① 给模型类型、系统自带类型,扩展public的成员 21 | - obj.kj.成员名称 22 | - 比如 "123".kj.numberCount, model.kj.JSON() 23 | 24 | ② 给模型类型、系统自带类型,扩展internal的成员 25 | - obj.kj_成员名称 26 | - 比如 "123".kj_numberCount, model.kj_JSON() 27 | 28 | ③ private\fileprivate的成员 29 | - obj._成员名称 30 | - 比如 "123"._numberCount, model._JSON() 31 | 32 | ④ 类型名、方法名必须是大写的`JSON` 33 | - 不要写`json`, 也不要写`Json` 34 | - 如果是变量名、常量名、参数名,可以用小写`json` 35 | 36 | ---------------------------------------------------------------------------- 37 | 38 | #### TestPass 39 | 40 | Please make all test cases right before pushing your code 41 | - include iOS Device\Simulator 42 | - include release\debug mode 43 | 44 | #### NamingConvention 45 | 46 | ① public extension for system types、model types 47 | - obj.kj.memberName 48 | - e.g. "123".kj.numberCount, model.kj.JSON() 49 | 50 | ② internal extension for system types、model types 51 | - obj.kj_memberName 52 | - e.g. "123".kj_numberCount, model.kj_JSON() 53 | 54 | ③ private\fileprivate 55 | - obj._memberName 56 | - e.g. "123"._numberCount, model._JSON() 57 | 58 | ④ Name of method or type must be `JSON` 59 | - not `json`, not `Json` 60 | - name of variable can be `json` 61 | -------------------------------------------------------------------------------- /KakaJSON.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "KakaJSON" 3 | s.version = "1.1.2" 4 | s.summary = "Fast conversion between JSON and model in Swift" 5 | s.homepage = "https://github.com/kakaopensource/KakaJSON" 6 | s.license = { :type => "MIT" } 7 | s.authors = { "MJ Lee" => "richermj123go@vip.qq.com" } 8 | 9 | s.requires_arc = true 10 | s.swift_version = "5.0" 11 | 12 | s.osx.deployment_target = "10.9" 13 | s.ios.deployment_target = "8.0" 14 | s.watchos.deployment_target = "2.0" 15 | s.tvos.deployment_target = "9.0" 16 | 17 | s.source = { :git => "https://github.com/kakaopensource/KakaJSON.git", :tag => s.version } 18 | s.source_files = "Sources/KakaJSON/**/*.swift" 19 | end -------------------------------------------------------------------------------- /KakaJSON.xcodeproj/KakaJSONTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /KakaJSON.xcodeproj/KakaJSON_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | FMWK 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /KakaJSON.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /KakaJSON.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /KakaJSON.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /KakaJSON.xcodeproj/xcshareddata/xcschemes/KakaJSON-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /KakaJSON.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /KakaJSON.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /KakaJSON.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /KakaJSONDemo/KakaJSONDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 627B5EAD2321063E0046E23D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627B5EAC2321063E0046E23D /* AppDelegate.swift */; }; 11 | 627B5EAF2321063E0046E23D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627B5EAE2321063E0046E23D /* ViewController.swift */; }; 12 | 627B5EB22321063E0046E23D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 627B5EB02321063E0046E23D /* Main.storyboard */; }; 13 | 627B5EB4232106400046E23D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 627B5EB3232106400046E23D /* Assets.xcassets */; }; 14 | 627B5EB7232106400046E23D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 627B5EB5232106400046E23D /* LaunchScreen.storyboard */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXCopyFilesBuildPhase section */ 18 | 627B5EC12321064A0046E23D /* Embed Frameworks */ = { 19 | isa = PBXCopyFilesBuildPhase; 20 | buildActionMask = 2147483647; 21 | dstPath = ""; 22 | dstSubfolderSpec = 10; 23 | files = ( 24 | ); 25 | name = "Embed Frameworks"; 26 | runOnlyForDeploymentPostprocessing = 0; 27 | }; 28 | /* End PBXCopyFilesBuildPhase section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 627B5EA92321063E0046E23D /* KakaJSONDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KakaJSONDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 627B5EAC2321063E0046E23D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | 627B5EAE2321063E0046E23D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 34 | 627B5EB12321063E0046E23D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | 627B5EB3232106400046E23D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | 627B5EB6232106400046E23D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | 627B5EB8232106400046E23D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | 627B5EA62321063E0046E23D /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | 627B5EA02321063E0046E23D = { 52 | isa = PBXGroup; 53 | children = ( 54 | 627B5EAB2321063E0046E23D /* KakaJSONDemo */, 55 | 627B5EAA2321063E0046E23D /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | 627B5EAA2321063E0046E23D /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 627B5EA92321063E0046E23D /* KakaJSONDemo.app */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | 627B5EAB2321063E0046E23D /* KakaJSONDemo */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 627B5EAC2321063E0046E23D /* AppDelegate.swift */, 71 | 627B5EAE2321063E0046E23D /* ViewController.swift */, 72 | 627B5EB02321063E0046E23D /* Main.storyboard */, 73 | 627B5EB3232106400046E23D /* Assets.xcassets */, 74 | 627B5EB5232106400046E23D /* LaunchScreen.storyboard */, 75 | 627B5EB8232106400046E23D /* Info.plist */, 76 | ); 77 | path = KakaJSONDemo; 78 | sourceTree = ""; 79 | }; 80 | /* End PBXGroup section */ 81 | 82 | /* Begin PBXNativeTarget section */ 83 | 627B5EA82321063E0046E23D /* KakaJSONDemo */ = { 84 | isa = PBXNativeTarget; 85 | buildConfigurationList = 627B5EBB232106400046E23D /* Build configuration list for PBXNativeTarget "KakaJSONDemo" */; 86 | buildPhases = ( 87 | 627B5EA52321063E0046E23D /* Sources */, 88 | 627B5EA62321063E0046E23D /* Frameworks */, 89 | 627B5EA72321063E0046E23D /* Resources */, 90 | 627B5EC12321064A0046E23D /* Embed Frameworks */, 91 | ); 92 | buildRules = ( 93 | ); 94 | dependencies = ( 95 | ); 96 | name = KakaJSONDemo; 97 | productName = KakaJSONDemo; 98 | productReference = 627B5EA92321063E0046E23D /* KakaJSONDemo.app */; 99 | productType = "com.apple.product-type.application"; 100 | }; 101 | /* End PBXNativeTarget section */ 102 | 103 | /* Begin PBXProject section */ 104 | 627B5EA12321063E0046E23D /* Project object */ = { 105 | isa = PBXProject; 106 | attributes = { 107 | LastSwiftUpdateCheck = 1020; 108 | LastUpgradeCheck = 1020; 109 | ORGANIZATIONNAME = KakaJSON; 110 | TargetAttributes = { 111 | 627B5EA82321063E0046E23D = { 112 | CreatedOnToolsVersion = 10.2; 113 | }; 114 | }; 115 | }; 116 | buildConfigurationList = 627B5EA42321063E0046E23D /* Build configuration list for PBXProject "KakaJSONDemo" */; 117 | compatibilityVersion = "Xcode 9.3"; 118 | developmentRegion = en; 119 | hasScannedForEncodings = 0; 120 | knownRegions = ( 121 | en, 122 | Base, 123 | ); 124 | mainGroup = 627B5EA02321063E0046E23D; 125 | productRefGroup = 627B5EAA2321063E0046E23D /* Products */; 126 | projectDirPath = ""; 127 | projectRoot = ""; 128 | targets = ( 129 | 627B5EA82321063E0046E23D /* KakaJSONDemo */, 130 | ); 131 | }; 132 | /* End PBXProject section */ 133 | 134 | /* Begin PBXResourcesBuildPhase section */ 135 | 627B5EA72321063E0046E23D /* Resources */ = { 136 | isa = PBXResourcesBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | 627B5EB7232106400046E23D /* LaunchScreen.storyboard in Resources */, 140 | 627B5EB4232106400046E23D /* Assets.xcassets in Resources */, 141 | 627B5EB22321063E0046E23D /* Main.storyboard in Resources */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | /* End PBXResourcesBuildPhase section */ 146 | 147 | /* Begin PBXSourcesBuildPhase section */ 148 | 627B5EA52321063E0046E23D /* Sources */ = { 149 | isa = PBXSourcesBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | 627B5EAF2321063E0046E23D /* ViewController.swift in Sources */, 153 | 627B5EAD2321063E0046E23D /* AppDelegate.swift in Sources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXSourcesBuildPhase section */ 158 | 159 | /* Begin PBXVariantGroup section */ 160 | 627B5EB02321063E0046E23D /* Main.storyboard */ = { 161 | isa = PBXVariantGroup; 162 | children = ( 163 | 627B5EB12321063E0046E23D /* Base */, 164 | ); 165 | name = Main.storyboard; 166 | sourceTree = ""; 167 | }; 168 | 627B5EB5232106400046E23D /* LaunchScreen.storyboard */ = { 169 | isa = PBXVariantGroup; 170 | children = ( 171 | 627B5EB6232106400046E23D /* Base */, 172 | ); 173 | name = LaunchScreen.storyboard; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXVariantGroup section */ 177 | 178 | /* Begin XCBuildConfiguration section */ 179 | 627B5EB9232106400046E23D /* Debug */ = { 180 | isa = XCBuildConfiguration; 181 | buildSettings = { 182 | ALWAYS_SEARCH_USER_PATHS = NO; 183 | CLANG_ANALYZER_NONNULL = YES; 184 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 185 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 186 | CLANG_CXX_LIBRARY = "libc++"; 187 | CLANG_ENABLE_MODULES = YES; 188 | CLANG_ENABLE_OBJC_ARC = YES; 189 | CLANG_ENABLE_OBJC_WEAK = YES; 190 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 191 | CLANG_WARN_BOOL_CONVERSION = YES; 192 | CLANG_WARN_COMMA = YES; 193 | CLANG_WARN_CONSTANT_CONVERSION = YES; 194 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 195 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 196 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 197 | CLANG_WARN_EMPTY_BODY = YES; 198 | CLANG_WARN_ENUM_CONVERSION = YES; 199 | CLANG_WARN_INFINITE_RECURSION = YES; 200 | CLANG_WARN_INT_CONVERSION = YES; 201 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 202 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 203 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 204 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 205 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 206 | CLANG_WARN_STRICT_PROTOTYPES = YES; 207 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 208 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 209 | CLANG_WARN_UNREACHABLE_CODE = YES; 210 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 211 | CODE_SIGN_IDENTITY = "iPhone Developer"; 212 | COPY_PHASE_STRIP = NO; 213 | DEBUG_INFORMATION_FORMAT = dwarf; 214 | ENABLE_STRICT_OBJC_MSGSEND = YES; 215 | ENABLE_TESTABILITY = YES; 216 | GCC_C_LANGUAGE_STANDARD = gnu11; 217 | GCC_DYNAMIC_NO_PIC = NO; 218 | GCC_NO_COMMON_BLOCKS = YES; 219 | GCC_OPTIMIZATION_LEVEL = 0; 220 | GCC_PREPROCESSOR_DEFINITIONS = ( 221 | "DEBUG=1", 222 | "$(inherited)", 223 | ); 224 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 225 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 226 | GCC_WARN_UNDECLARED_SELECTOR = YES; 227 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 228 | GCC_WARN_UNUSED_FUNCTION = YES; 229 | GCC_WARN_UNUSED_VARIABLE = YES; 230 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 231 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 232 | MTL_FAST_MATH = YES; 233 | ONLY_ACTIVE_ARCH = YES; 234 | SDKROOT = iphoneos; 235 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 236 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 237 | }; 238 | name = Debug; 239 | }; 240 | 627B5EBA232106400046E23D /* Release */ = { 241 | isa = XCBuildConfiguration; 242 | buildSettings = { 243 | ALWAYS_SEARCH_USER_PATHS = NO; 244 | CLANG_ANALYZER_NONNULL = YES; 245 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_ENABLE_OBJC_WEAK = YES; 251 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 252 | CLANG_WARN_BOOL_CONVERSION = YES; 253 | CLANG_WARN_COMMA = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | CODE_SIGN_IDENTITY = "iPhone Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 275 | ENABLE_NS_ASSERTIONS = NO; 276 | ENABLE_STRICT_OBJC_MSGSEND = YES; 277 | GCC_C_LANGUAGE_STANDARD = gnu11; 278 | GCC_NO_COMMON_BLOCKS = YES; 279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 283 | GCC_WARN_UNUSED_FUNCTION = YES; 284 | GCC_WARN_UNUSED_VARIABLE = YES; 285 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 286 | MTL_ENABLE_DEBUG_INFO = NO; 287 | MTL_FAST_MATH = YES; 288 | SDKROOT = iphoneos; 289 | SWIFT_COMPILATION_MODE = wholemodule; 290 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 291 | VALIDATE_PRODUCT = YES; 292 | }; 293 | name = Release; 294 | }; 295 | 627B5EBC232106400046E23D /* Debug */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 299 | CODE_SIGN_STYLE = Automatic; 300 | INFOPLIST_FILE = KakaJSONDemo/Info.plist; 301 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 302 | LD_RUNPATH_SEARCH_PATHS = ( 303 | "$(inherited)", 304 | "@executable_path/Frameworks", 305 | ); 306 | PRODUCT_BUNDLE_IDENTIFIER = com.github.kakaopensource.KakaJSONDemo; 307 | PRODUCT_NAME = "$(TARGET_NAME)"; 308 | SWIFT_VERSION = 5.0; 309 | TARGETED_DEVICE_FAMILY = "1,2"; 310 | }; 311 | name = Debug; 312 | }; 313 | 627B5EBD232106400046E23D /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 317 | CODE_SIGN_STYLE = Automatic; 318 | INFOPLIST_FILE = KakaJSONDemo/Info.plist; 319 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 320 | LD_RUNPATH_SEARCH_PATHS = ( 321 | "$(inherited)", 322 | "@executable_path/Frameworks", 323 | ); 324 | PRODUCT_BUNDLE_IDENTIFIER = com.github.kakaopensource.KakaJSONDemo; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | SWIFT_VERSION = 5.0; 327 | TARGETED_DEVICE_FAMILY = "1,2"; 328 | }; 329 | name = Release; 330 | }; 331 | /* End XCBuildConfiguration section */ 332 | 333 | /* Begin XCConfigurationList section */ 334 | 627B5EA42321063E0046E23D /* Build configuration list for PBXProject "KakaJSONDemo" */ = { 335 | isa = XCConfigurationList; 336 | buildConfigurations = ( 337 | 627B5EB9232106400046E23D /* Debug */, 338 | 627B5EBA232106400046E23D /* Release */, 339 | ); 340 | defaultConfigurationIsVisible = 0; 341 | defaultConfigurationName = Release; 342 | }; 343 | 627B5EBB232106400046E23D /* Build configuration list for PBXNativeTarget "KakaJSONDemo" */ = { 344 | isa = XCConfigurationList; 345 | buildConfigurations = ( 346 | 627B5EBC232106400046E23D /* Debug */, 347 | 627B5EBD232106400046E23D /* Release */, 348 | ); 349 | defaultConfigurationIsVisible = 0; 350 | defaultConfigurationName = Release; 351 | }; 352 | /* End XCConfigurationList section */ 353 | }; 354 | rootObject = 627B5EA12321063E0046E23D /* Project object */; 355 | } 356 | -------------------------------------------------------------------------------- /KakaJSONDemo/KakaJSONDemo.xcodeproj/xcshareddata/xcschemes/KakaJSONDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /KakaJSONDemo/KakaJSONDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // KakaJSONDemo 4 | // 5 | // Created by Quentin MED on 2019/9/5. 6 | // Copyright © 2019 KakaJSON. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /KakaJSONDemo/KakaJSONDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /KakaJSONDemo/KakaJSONDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /KakaJSONDemo/KakaJSONDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /KakaJSONDemo/KakaJSONDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /KakaJSONDemo/KakaJSONDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /KakaJSONDemo/KakaJSONDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // KakaJSONDemo 4 | // 5 | // Created by Quentin MED on 2019/9/5. 6 | // Copyright © 2019 KakaJSON. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import KakaJSON 11 | 12 | class ViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | struct Repo: Convertible { 18 | var name: String? 19 | var url: URL? 20 | } 21 | 22 | let json = [ 23 | "name": "KakaJSON", 24 | "url": "https://github.com/kakaopensource/KakaJSON" 25 | ] 26 | 27 | let repo = json.kj.model(Repo.self) 28 | print(repo) 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 KakaOpenSource 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "KakaJSON", 8 | platforms: [ 9 | .macOS(.v10_10), 10 | .iOS(.v8), 11 | .tvOS(.v9), 12 | .watchOS(.v2) 13 | ], 14 | products: [ 15 | .library( 16 | name: "KakaJSON", 17 | targets: ["KakaJSON"]), 18 | ], 19 | targets: [ 20 | .target( 21 | name: "KakaJSON", 22 | dependencies: []), 23 | .testTarget( 24 | name: "KakaJSONTests", 25 | dependencies: ["KakaJSON"]), 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Convert/Convertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Convertible.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/11. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Convertible Interface 12 | 13 | /// return value type of kj_modelKey 14 | public protocol ModelPropertyKey {} 15 | extension String: ModelPropertyKey {} 16 | extension Array: ModelPropertyKey where Element == String {} 17 | 18 | /// return value type of kj_JSONKey 19 | public typealias JSONPropertyKey = String 20 | 21 | /// all the models who want to convert should conform to `Convertible` 22 | /// please let struct or class conform to `Convertible` 23 | public protocol Convertible { 24 | init() 25 | 26 | /// Get a key from propertyName when converting JSON to model 27 | /// 28 | /// Only call once for every property in every type 29 | /// 30 | /// - Parameter property: A property of model 31 | /// 32 | /// - Returns: Return a key to get jsonValue for the property 33 | func kj_modelKey(from property: Property) -> ModelPropertyKey 34 | 35 | /// Get a model modelValue from jsonValue when converting JSON to model 36 | /// 37 | /// - Parameters: 38 | /// - jsonValue: A jsonValue for the property 39 | /// - property: A property of model 40 | /// 41 | /// - Returns: return `nil` indicates ignore the property, 42 | /// use the initial value instead. 43 | /// return `JSONValue` indicates do nothing 44 | func kj_modelValue(from jsonValue: Any?, 45 | _ property: Property) -> Any? 46 | 47 | /// model type for Any、AnyObject、Convertible... 48 | /// 49 | /// - Parameters: 50 | /// - jsonValue: A jsonValue for the property 51 | /// - property: A property of model 52 | func kj_modelType(from jsonValue: Any?, 53 | _ property: Property) -> Convertible.Type? 54 | 55 | /// call when will begin to convert from JSON to model 56 | /// 57 | /// - Parameters: 58 | /// - json: A json will be converted to model 59 | mutating func kj_willConvertToModel(from json: [String: Any]) 60 | 61 | /// call when did finish converting from JSON to model 62 | /// 63 | /// - Parameters: 64 | /// - json: A json did be converted to model 65 | mutating func kj_didConvertToModel(from json: [String: Any]) 66 | 67 | /// Get a key from propertyName when converting from model to JSON 68 | /// 69 | /// Only call once for every property in every type 70 | /// 71 | /// - Parameters: 72 | /// - property: A property of model 73 | func kj_JSONKey(from property: Property) -> JSONPropertyKey 74 | 75 | /// Get a JSONValue from modelValue when converting from JSON to model 76 | /// 77 | /// - Parameters: 78 | /// - modelValue: A modelValue for the property 79 | /// - property: A property of model 80 | /// 81 | /// - Returns: return `nil` indicates ignore the JSONValue. 82 | /// return `modelValue` indicates do nothing 83 | func kj_JSONValue(from modelValue: Any?, 84 | _ property: Property) -> Any? 85 | 86 | /// call when will begin to convert from model to JSON 87 | func kj_willConvertToJSON() 88 | 89 | /// call when did finish converting from model to JSON 90 | /// 91 | /// - Parameters: 92 | /// - json: A json did be converted from model 93 | func kj_didConvertToJSON(json: [String: Any]) 94 | } 95 | 96 | /// default implementation 97 | public extension Convertible { 98 | func kj_modelKey(from property: Property) -> ModelPropertyKey { 99 | return ConvertibleConfig.modelKey(from: property, Self.self) 100 | } 101 | func kj_modelValue(from jsonValue: Any?, 102 | _ property: Property) -> Any? { 103 | return ConvertibleConfig.modelValue(from: jsonValue, property, Self.self) 104 | } 105 | func kj_modelType(from jsonValue: Any?, 106 | _ property: Property) -> Convertible.Type? { return nil } 107 | func kj_willConvertToModel(from json: [String: Any]) {} 108 | func kj_didConvertToModel(from json: [String: Any]) {} 109 | 110 | func kj_JSONKey(from property: Property) -> JSONPropertyKey { 111 | return ConvertibleConfig.JSONKey(from: property, Self.self) 112 | } 113 | func kj_JSONValue(from modelValue: Any?, 114 | _ property: Property) -> Any? { 115 | return ConvertibleConfig.JSONValue(from: modelValue, property, Self.self) 116 | } 117 | func kj_willConvertToJSON() {} 118 | func kj_didConvertToJSON(json: [String: Any]) {} 119 | } 120 | 121 | // MARK: - Wrapper for Convertible 122 | public extension Convertible { 123 | static var kj: ConvertibleKJ.Type { 124 | get { return ConvertibleKJ.self } 125 | set {} 126 | } 127 | var kj: ConvertibleKJ { 128 | get { return ConvertibleKJ(self) } 129 | set {} 130 | } 131 | 132 | /// mutable version of kj 133 | var kj_m: ConvertibleKJ_M { 134 | mutating get { return ConvertibleKJ_M(&self) } 135 | set {} 136 | } 137 | } 138 | 139 | public struct ConvertibleKJ_M { 140 | var basePtr: UnsafeMutablePointer 141 | init(_ basePtr: UnsafeMutablePointer) { 142 | self.basePtr = basePtr 143 | } 144 | 145 | /// JSONData -> Model 146 | public func convert(from jsonData: Data) { 147 | basePtr.pointee.kj_convert(from: jsonData) 148 | } 149 | 150 | /// JSONData -> Model 151 | public func convert(from jsonData: NSData) { 152 | basePtr.pointee.kj_convert(from: jsonData as Data) 153 | } 154 | 155 | /// JSONString -> Model 156 | public func convert(from jsonString: String) { 157 | basePtr.pointee.kj_convert(from: jsonString) 158 | } 159 | 160 | /// JSONString -> Model 161 | public func convert(from jsonString: NSString) { 162 | basePtr.pointee.kj_convert(from: jsonString as String) 163 | } 164 | 165 | /// JSONObject -> Model 166 | public func convert(from json: [String: Any]) { 167 | basePtr.pointee.kj_convert(from: json) 168 | } 169 | 170 | /// JSONObject -> Model 171 | public func convert(from json: [NSString: Any]) { 172 | basePtr.pointee.kj_convert(from: json as [String: Any]) 173 | } 174 | 175 | /// JSONObject -> Model 176 | public func convert(from json: NSDictionary) { 177 | basePtr.pointee.kj_convert(from: json as! [String: Any]) 178 | } 179 | } 180 | 181 | public struct ConvertibleKJ { 182 | var base: T 183 | init(_ base: T) { 184 | self.base = base 185 | } 186 | 187 | /// Model -> JSONObject 188 | public func JSONObject() -> [String: Any] { 189 | return base.kj_JSONObject() 190 | } 191 | 192 | /// Model -> JSONString 193 | public func JSONString(prettyPrinted: Bool = false) -> String { 194 | return base.kj_JSONString(prettyPrinted: prettyPrinted) 195 | } 196 | } 197 | 198 | private extension Convertible { 199 | /// get the ponter of model 200 | mutating func _ptr() -> UnsafeMutableRawPointer { 201 | return (Metadata.type(self)!.kind == .struct) 202 | ? withUnsafeMutablePointer(to: &self) { UnsafeMutableRawPointer($0) } 203 | : self ~>> UnsafeMutableRawPointer.self 204 | } 205 | } 206 | 207 | // MARK: - JSON -> Model 208 | extension Convertible { 209 | mutating func kj_convert(from jsonData: Data) { 210 | if let json = JSONSerialization.kj_JSON(jsonData, [String: Any].self) { 211 | kj_convert(from: json) 212 | return 213 | } 214 | Logger.error("Failed to get JSON from JSONData.") 215 | } 216 | 217 | mutating func kj_convert(from jsonString: String) { 218 | if let json = JSONSerialization.kj_JSON(jsonString, [String: Any].self) { 219 | kj_convert(from: json) 220 | return 221 | } 222 | Logger.error("Failed to get JSON from JSONString.") 223 | } 224 | 225 | mutating func kj_convert(from json: [String: Any]) { 226 | guard let mt = Metadata.type(self) as? ModelType else { 227 | Logger.warnning("Not a class or struct instance.") 228 | return 229 | } 230 | guard let properties = mt.properties else { 231 | Logger.warnning("Don't have any property.") 232 | return 233 | } 234 | 235 | // get data address 236 | let model = _ptr() 237 | 238 | kj_willConvertToModel(from: json) 239 | 240 | // enumerate properties 241 | for property in properties { 242 | // key filter 243 | let key = mt.modelKey(from: property.name, 244 | kj_modelKey(from: property)) 245 | 246 | // value filter 247 | guard let newValue = kj_modelValue( 248 | from: json.kj_value(for: key), 249 | property)~! else { continue } 250 | 251 | let propertyType = property.dataType 252 | // if they are the same type, set value directly 253 | if Swift.type(of: newValue) == propertyType { 254 | property.set(newValue, for: model) 255 | continue 256 | } 257 | 258 | // Model Type have priority 259 | // it can return subclass object to match superclass type 260 | if let modelType = kj_modelType(from: newValue, property), 261 | let value = _modelTypeValue(newValue, modelType, propertyType) { 262 | property.set(value, for: model) 263 | continue 264 | } 265 | 266 | // try to convert newValue to propertyType 267 | guard let value = Values.value(newValue, 268 | propertyType, 269 | property.get(from: model)) else { 270 | property.set(newValue, for: model) 271 | continue 272 | } 273 | 274 | property.set(value, for: model) 275 | } 276 | 277 | kj_didConvertToModel(from: json) 278 | } 279 | 280 | private mutating 281 | func _modelTypeValue(_ jsonValue: Any, 282 | _ modelType: Convertible.Type, 283 | _ propertyType: Any.Type) -> Any? { 284 | // don't use `propertyType is XX.Type` 285 | // because it may be an `Any` type 286 | if let json = jsonValue as? [Any] { 287 | let models = json.kj.modelArray(type: modelType) 288 | if !models.isEmpty { 289 | return propertyType is NSMutableArray.Type 290 | ? NSMutableArray(array: models) 291 | : models 292 | } 293 | } 294 | 295 | if let json = jsonValue as? [String: Any] { 296 | if let jsonDict = jsonValue as? [String: [String: Any]?] { 297 | var modelDict = [String: Any]() 298 | for (k, v) in jsonDict { 299 | guard let m = v?.kj.model(type: modelType) else { continue } 300 | modelDict[k] = m 301 | } 302 | guard modelDict.count > 0 else { return jsonValue } 303 | 304 | return propertyType is NSMutableDictionary.Type 305 | ? NSMutableDictionary(dictionary: modelDict) 306 | : modelDict 307 | } else { 308 | return json.kj.model(type: modelType) 309 | } 310 | } 311 | return jsonValue 312 | } 313 | } 314 | 315 | // MARK: - Model -> JSON 316 | extension Convertible { 317 | func kj_JSONObject() -> [String: Any] { 318 | var json = [String: Any]() 319 | guard let mt = Metadata.type(self) as? ModelType else { 320 | Logger.warnning("Not a class or struct instance.") 321 | return json 322 | } 323 | guard let properties = mt.properties else { 324 | Logger.warnning("Don't have any property.") 325 | return json 326 | } 327 | 328 | kj_willConvertToJSON() 329 | 330 | // as AnyObject is important! for class if xx is protocol type 331 | // var model = xx as AnyObject 332 | var model = self 333 | 334 | // get data address 335 | let ptr = model._ptr() 336 | 337 | // get JSON from model 338 | for property in properties { 339 | // value filter 340 | guard let value = kj_JSONValue( 341 | from: property.get(from: ptr)~!, 342 | property)~! else { continue } 343 | 344 | guard let v = Values.JSONValue(value) else { continue } 345 | 346 | // key filter 347 | json[mt.JSONKey(from: property.name, 348 | kj_JSONKey(from: property))] = v 349 | } 350 | 351 | kj_didConvertToJSON(json: json) 352 | 353 | return json 354 | } 355 | 356 | func kj_JSONString(prettyPrinted: Bool = false) -> String { 357 | if let str = JSONSerialization.kj_string(kj_JSONObject() as Any, 358 | prettyPrinted: prettyPrinted) { 359 | return str 360 | } 361 | Logger.error("Failed to get JSONString from JSON.") 362 | return "" 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Convert/ConvertibleConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConvertibleConfig.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/15. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias ModelKeyConfig = (Property) -> ModelPropertyKey 12 | public typealias ModelValueConfig = (Any?, Property) -> Any? 13 | public typealias JSONKeyConfig = (Property) -> JSONPropertyKey 14 | public typealias JSONValueConfig = (Any?, Property) -> Any? 15 | 16 | /// Config for convertible 17 | /// 18 | /// Suggest: 19 | /// 1. Set every config once for every type 20 | /// 2. Set config after app finish lauching 21 | public class ConvertibleConfig { 22 | private class Item { 23 | var modelKey: ModelKeyConfig? 24 | var modelValue: ModelValueConfig? 25 | var jsonKey: JSONKeyConfig? 26 | var jsonValue: JSONValueConfig? 27 | init() {} 28 | init(modelKey: @escaping ModelKeyConfig) { self.modelKey = modelKey } 29 | init(modelValue: @escaping ModelValueConfig) { self.modelValue = modelValue } 30 | init(jsonKey: @escaping JSONKeyConfig) { self.jsonKey = jsonKey } 31 | init(jsonValue: @escaping JSONValueConfig) { self.jsonValue = jsonValue } 32 | } 33 | 34 | private static let global = Item() 35 | private static var items: [TypeKey: Item] = [:] 36 | 37 | /// get global config for modelKey 38 | public static func modelKey(from property: Property) -> ModelPropertyKey { 39 | guard let fn = global.modelKey else { return property.name } 40 | return fn(property) 41 | } 42 | 43 | /// get model's config for modelKey 44 | public static func modelKey(from property: Property, 45 | _ model: Convertible) -> ModelPropertyKey { 46 | return modelKey(from: property, type(of: model)) 47 | } 48 | 49 | /// get type's config for modelKey 50 | public static func modelKey(from property: Property, 51 | _ type: Convertible.Type) -> ModelPropertyKey { 52 | if let fn = items[typeKey(type)]?.modelKey { 53 | return fn(property) 54 | } 55 | 56 | let mt = Metadata.type(type) 57 | if var classMt = mt as? ClassType { 58 | while let superMt = classMt.super { 59 | if let fn = items[typeKey(superMt.type)]?.modelKey { 60 | return fn(property) 61 | } 62 | classMt = superMt 63 | } 64 | } 65 | 66 | guard let fn = global.modelKey else { 67 | return property.name 68 | } 69 | return fn(property) 70 | } 71 | 72 | /// get global config for modelValue 73 | public static func modelValue(from jsonValue: Any?, 74 | _ property: Property) -> Any? { 75 | guard let fn = global.modelValue else { return jsonValue } 76 | return fn(jsonValue, property) 77 | } 78 | 79 | /// get model's config for modelValue 80 | public static func modelValue(from jsonValue: Any?, 81 | _ property: Property, 82 | _ model: Convertible) -> Any? { 83 | return modelValue(from: jsonValue, property, type(of: model)) 84 | } 85 | 86 | /// get type's config for modelValue 87 | public static func modelValue(from jsonValue: Any?, 88 | _ property: Property, 89 | _ type: Convertible.Type) -> Any? { 90 | let key = typeKey(type) 91 | if let fn = items[key]?.modelValue { 92 | return fn(jsonValue, property) 93 | } 94 | 95 | let mt = Metadata.type(type) 96 | if var classMt = mt as? ClassType { 97 | while let superMt = classMt.super { 98 | if let fn = items[typeKey(superMt.type)]?.modelValue { 99 | items[key]?.modelValue = fn 100 | return fn(jsonValue, property) 101 | } 102 | classMt = superMt 103 | } 104 | } 105 | 106 | guard let fn = global.modelValue else { 107 | items[key]?.modelValue = { v, _ in v } 108 | return jsonValue 109 | } 110 | items[key]?.modelValue = fn 111 | return fn(jsonValue, property) 112 | } 113 | 114 | /// get global config for JSONKey 115 | public static func JSONKey(from property: Property) -> JSONPropertyKey { 116 | guard let fn = global.jsonKey else { return property.name } 117 | return fn(property) 118 | } 119 | 120 | /// get model's config for JSONKey 121 | public static func JSONKey(from property: Property, 122 | _ model: Convertible) -> JSONPropertyKey { 123 | return JSONKey(from: property, type(of: model)) 124 | } 125 | 126 | /// get type's config for JSONKey 127 | public static func JSONKey(from property: Property, 128 | _ type: Convertible.Type) -> JSONPropertyKey { 129 | if let fn = items[typeKey(type)]?.jsonKey { 130 | return fn(property) 131 | } 132 | 133 | let mt = Metadata.type(type) 134 | if var classMt = mt as? ClassType { 135 | while let superMt = classMt.super { 136 | if let fn = items[typeKey(superMt.type)]?.jsonKey { 137 | return fn(property) 138 | } 139 | classMt = superMt 140 | } 141 | } 142 | 143 | guard let fn = global.jsonKey else { 144 | return property.name 145 | } 146 | return fn(property) 147 | } 148 | 149 | /// get global config for JSONValue 150 | public static func JSONValue(from modelValue: Any?, 151 | _ property: Property) -> Any? { 152 | guard let fn = global.modelValue else { return modelValue } 153 | return fn(modelValue, property) 154 | } 155 | 156 | /// get model's config for JSONValue 157 | public static func JSONValue(from modelValue: Any?, 158 | _ property: Property, 159 | _ model: Convertible) -> Any? { 160 | return JSONValue(from: modelValue, property, type(of: model)) 161 | } 162 | 163 | /// get type's config for JSONValue 164 | public static func JSONValue(from modelValue: Any?, 165 | _ property: Property, 166 | _ type: Convertible.Type) -> Any? { 167 | let key = typeKey(type) 168 | if let fn = items[key]?.jsonValue { 169 | return fn(modelValue, property) 170 | } 171 | 172 | let mt = Metadata.type(type) 173 | if var classMt = mt as? ClassType { 174 | while let superMt = classMt.super { 175 | if let fn = items[typeKey(superMt.type)]?.jsonValue { 176 | items[key]?.jsonValue = fn 177 | return fn(modelValue, property) 178 | } 179 | classMt = superMt 180 | } 181 | } 182 | 183 | guard let fn = global.modelValue else { 184 | items[key]?.jsonValue = { v, _ in v} 185 | return modelValue 186 | } 187 | items[key]?.jsonValue = fn 188 | return fn(modelValue, property) 189 | } 190 | 191 | /// set type's config for modelKey 192 | public static func setModelKey(for type: Convertible.Type, 193 | _ modelKey: @escaping ModelKeyConfig) { 194 | setModelKey(for: [type], modelKey) 195 | } 196 | 197 | /// set types's config for modelKey 198 | public static func setModelKey(for types: [Convertible.Type] = [], 199 | _ modelKey: @escaping ModelKeyConfig) { 200 | if types.count == 0 { 201 | global.modelKey = modelKey 202 | return 203 | } 204 | 205 | types.forEach { 206 | let key = typeKey($0) 207 | if let item = items[key] { 208 | item.modelKey = modelKey 209 | return 210 | } 211 | items[key] = Item(modelKey: modelKey) 212 | } 213 | } 214 | 215 | /// set type's config for modelValue 216 | public static func setModelValue(for type: Convertible.Type, 217 | modelValue: @escaping ModelValueConfig) { 218 | setModelValue(for: [type], modelValue: modelValue) 219 | } 220 | 221 | /// set types's config for modelValue 222 | public static func setModelValue(for types: [Convertible.Type] = [], 223 | modelValue: @escaping ModelValueConfig) { 224 | if types.count == 0 { 225 | global.modelValue = modelValue 226 | return 227 | } 228 | 229 | types.forEach { 230 | let key = typeKey($0) 231 | if let item = items[key] { 232 | item.modelValue = modelValue 233 | return 234 | } 235 | items[key] = Item(modelValue: modelValue) 236 | } 237 | } 238 | 239 | /// set type's config for jsonKey 240 | public static func setJSONKey(for type: Convertible.Type, 241 | jsonKey: @escaping JSONKeyConfig) { 242 | setJSONKey(for: [type], jsonKey: jsonKey) 243 | } 244 | 245 | /// set types's config for jsonKey 246 | public static func setJSONKey(for types: [Convertible.Type] = [], 247 | jsonKey: @escaping JSONKeyConfig) { 248 | if types.count == 0 { 249 | global.jsonKey = jsonKey 250 | return 251 | } 252 | 253 | types.forEach { 254 | let key = typeKey($0) 255 | if let item = items[key] { 256 | item.jsonKey = jsonKey 257 | return 258 | } 259 | items[key] = Item(jsonKey: jsonKey) 260 | } 261 | } 262 | 263 | /// set type's config for jsonValue 264 | public static func setJSONValue(for type: Convertible.Type, 265 | jsonValue: @escaping JSONValueConfig) { 266 | setJSONValue(for: [type], jsonValue: jsonValue) 267 | } 268 | 269 | /// set types's config for jsonValue 270 | public static func setJSONValue(for types: [Convertible.Type] = [], 271 | jsonValue: @escaping JSONValueConfig) { 272 | if types.count == 0 { 273 | global.jsonValue = jsonValue 274 | return 275 | } 276 | 277 | types.forEach { 278 | let key = typeKey($0) 279 | if let item = items[key] { 280 | item.jsonValue = jsonValue 281 | return 282 | } 283 | items[key] = Item(jsonValue: jsonValue) 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Convert/ConvertibleEnum.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConvertibleEnum.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/13. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | public protocol ConvertibleEnum { 10 | static func kj_convert(from value: Any) -> Self? 11 | var kj_value: Any { get } 12 | static var kj_valueType: Any.Type { get } 13 | } 14 | 15 | public extension RawRepresentable where Self: ConvertibleEnum { 16 | static func kj_convert(from value: Any) -> Self? { 17 | return (value as? RawValue).flatMap { Self.init(rawValue: $0) } 18 | } 19 | var kj_value: Any { return rawValue } 20 | static var kj_valueType: Any.Type { return RawValue.self } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Convert/KJ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KJ.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/30. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | public struct KJ { 10 | let base: Base 11 | init(_ base: Base) { 12 | self.base = base 13 | } 14 | } 15 | 16 | // MARK: - protocol for normal types 17 | public protocol KJCompatible {} 18 | public extension KJCompatible { 19 | static var kj: KJ.Type { 20 | get { return KJ.self } 21 | set {} 22 | } 23 | var kj: KJ { 24 | get { return KJ(self) } 25 | set {} 26 | } 27 | } 28 | 29 | // MARK: - protocol for types with a generic parameter 30 | public struct KJGeneric { 31 | let base: Base 32 | init(_ base: Base) { 33 | self.base = base 34 | } 35 | } 36 | public protocol KJGenericCompatible { 37 | associatedtype T 38 | } 39 | public extension KJGenericCompatible { 40 | static var kj: KJGeneric.Type { 41 | get { return KJGeneric.self } 42 | set {} 43 | } 44 | var kj: KJGeneric { 45 | get { return KJGeneric(self) } 46 | set {} 47 | } 48 | } 49 | 50 | // MARK: - protocol for types with two generic parameter2 51 | public struct KJGeneric2 { 52 | let base: Base 53 | init(_ base: Base) { 54 | self.base = base 55 | } 56 | } 57 | public protocol KJGenericCompatible2 { 58 | associatedtype T1 59 | associatedtype T2 60 | } 61 | public extension KJGenericCompatible2 { 62 | static var kj: KJGeneric2.Type { 63 | get { return KJGeneric2.self } 64 | set {} 65 | } 66 | var kj: KJGeneric2 { 67 | get { return KJGeneric2(self) } 68 | set {} 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Convert/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/2. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Logger { 12 | private static func _log(_ title: String, _ msg: T, _ file: NSString, 13 | _ fn: String, _ line: Int) { 14 | #if DEBUG 15 | print("「 KakaJSON 」\(title) \(file.lastPathComponent) >> \(line) >> \(fn) :\(msg)") 16 | #endif 17 | } 18 | 19 | static func error(_ msg: T, 20 | _ file: NSString = #file, 21 | _ fn: String = #function, 22 | _ line: Int = #line) { 23 | _log("❌", msg, file, fn, line) 24 | } 25 | 26 | static func warnning(_ msg: T, 27 | _ file: NSString = #file, 28 | _ fn: String = #function, 29 | _ line: Int = #line) { 30 | _log("⚠️", msg, file, fn, line) 31 | } 32 | 33 | static func success(_ msg: T, 34 | _ file: NSString = #file, 35 | _ fn: String = #function, 36 | _ line: Int = #line) { 37 | _log("✔️", msg, file, fn, line) 38 | } 39 | 40 | static func info(_ msg: T, 41 | _ file: NSString = #file, 42 | _ fn: String = #function, 43 | _ line: Int = #line) { 44 | _log("✔️", msg, file, fn, line) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Convert/TypeProxy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TypeProxy.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/13. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | protocol TypeProxy: Convertible {} 10 | 11 | extension TypeProxy { 12 | static func `is`(_ value: Any) -> Bool { 13 | return value is Self 14 | } 15 | 16 | static func `is`(_ type: Any.Type) -> Bool { 17 | return type is Self.Type 18 | } 19 | 20 | static func `as`(_ value: Any) -> Self? { 21 | return value as? Self 22 | } 23 | 24 | static func `as`(_ type: Any.Type) -> Self.Type? { 25 | return type as? Self.Type 26 | } 27 | } 28 | 29 | func typeProxy(_ type: Any.Type) -> TypeProxy.Type { 30 | // Any.Type(8 bytes) + Int(8 bytes) == Protocol.Type(16 bytes) 31 | return (type, 0) ~>> TypeProxy.Type.self 32 | } 33 | 34 | typealias TypeKey = UInt 35 | func typeKey(_ type: Any.Type) -> TypeKey { 36 | return type ~>> TypeKey.self 37 | } 38 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Extension/Array+KJ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+KJ.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/12. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSArray: KJCompatible {} 12 | extension Array: KJCompatible {} 13 | extension Set: KJCompatible {} 14 | extension NSSet: KJCompatible {} 15 | 16 | public extension KJ where Base: ExpressibleByArrayLiteral & Sequence { 17 | /// JSONObjectArray -> EnumArray 18 | func enumArray(_ type: M.Type) -> [M] { 19 | return enumArray(type: type) as! [M] 20 | } 21 | 22 | /// JSONObjectArray -> EnumArray 23 | func enumArray(type: ConvertibleEnum.Type) -> [ConvertibleEnum] { 24 | guard let _ = Metadata.type(type) as? EnumType else { return [] } 25 | return base.compactMap { 26 | let vv = Values.value($0, type.kj_valueType) 27 | return type.kj_convert(from: vv as Any) 28 | } 29 | } 30 | 31 | /// JSONObjectArray -> ModelArray 32 | func modelArray(_ type: M.Type) -> [M] { 33 | return modelArray(type: type) as! [M] 34 | } 35 | 36 | /// JSONObjectArray -> ModelArray 37 | func modelArray(type: Convertible.Type) -> [Convertible] { 38 | guard let mt = Metadata.type(type) as? ModelType, 39 | let _ = mt.properties 40 | else { return [] } 41 | return base.compactMap { 42 | switch $0 { 43 | case let dict as [String: Any]: return dict.kj_fastModel(type) 44 | case let dict as Data: return dict.kj_fastModel(type) 45 | case let dict as String: return dict.kj_fastModel(type) 46 | default: return nil 47 | } 48 | } 49 | } 50 | 51 | /// ModelArray -> JSONObjectArray 52 | func JSONObjectArray() -> [[String: Any]] { 53 | return base.compactMap { 54 | ($0~! as? Convertible)?.kj_JSONObject() 55 | } 56 | } 57 | 58 | /// Array -> JSONArray 59 | func JSONArray() -> [Any] { 60 | return Values.JSONValue(base) as! [Any] 61 | } 62 | 63 | /// Array -> JSONString 64 | func JSONString(prettyPrinted: Bool = false) -> String { 65 | if let str = JSONSerialization.kj_string(JSONArray(), 66 | prettyPrinted: prettyPrinted) { 67 | return str 68 | } 69 | Logger.error("Failed to get JSONString from JSON.") 70 | return "" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Extension/Data+KJ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+KJ.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/18. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Data: KJCompatible {} 12 | extension NSData: KJCompatible {} 13 | 14 | public extension KJ where Base == Data { 15 | /// JSONObject -> Model 16 | func model(_ type: M.Type) -> M? { 17 | return model(type: type) as? M 18 | } 19 | 20 | /// JSONObject -> Model 21 | func model(type: Convertible.Type) -> Convertible? { 22 | if let json = JSONSerialization.kj_JSON(base, [String: Any].self) { 23 | return json.kj.model(type: type) 24 | } 25 | Logger.error("Failed to get JSON from JSONData.") 26 | return nil 27 | } 28 | 29 | /// JSONObjectArray -> ModelArray 30 | func modelArray(_ type: M.Type) -> [M] { 31 | return modelArray(type: type) as! [M] 32 | } 33 | 34 | /// JSONObjectArray -> ModelArray 35 | func modelArray(type: Convertible.Type) -> [Convertible] { 36 | if let json = JSONSerialization.kj_JSON(base, [Any].self) { 37 | return json.kj.modelArray(type: type) 38 | } 39 | Logger.error("Failed to get JSON from JSONData.") 40 | return [] 41 | } 42 | } 43 | 44 | public extension KJ where Base: NSData { 45 | /// JSONObject -> Model 46 | func model(_ type: M.Type) -> M? { 47 | return (base as Data).kj.model(type) 48 | } 49 | 50 | /// JSONObject -> Model 51 | func model(type: Convertible.Type) -> Convertible? { 52 | return (base as Data).kj.model(type: type) 53 | } 54 | 55 | /// JSONObjectArray -> ModelArray 56 | func modelArray(_ type: M.Type) -> [M] { 57 | return (base as Data).kj.modelArray(type) 58 | } 59 | 60 | /// JSONObjectArray -> ModelArray 61 | func modelArray(type: Convertible.Type) -> [Convertible] { 62 | return (base as Data).kj.modelArray(type: type) 63 | } 64 | } 65 | 66 | extension Data { 67 | func kj_fastModel(_ type: Convertible.Type) -> Convertible? { 68 | if let json = JSONSerialization.kj_JSON(self, [String: Any].self) { 69 | return json.kj_fastModel(type) 70 | } 71 | Logger.error("Failed to get JSON from JSONData.") 72 | return nil 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Extension/Dictionary+KJ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+KJ.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/7. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSDictionary: KJCompatible {} 12 | extension Dictionary: KJGenericCompatible { 13 | public typealias T = Value 14 | } 15 | 16 | public extension KJGeneric where Base == [String: T] { 17 | /// JSONObject -> Model 18 | func model(_ type: M.Type) -> M { 19 | return model(type: type) as! M 20 | } 21 | 22 | /// JSONObject -> Model 23 | func model(type: Convertible.Type) -> Convertible { 24 | return base.kj_fastModel(type) 25 | } 26 | } 27 | 28 | public extension KJGeneric where Base == [NSString: T] { 29 | /// JSONObject -> Model 30 | func model(_ type: M.Type) -> M { 31 | return (base as [String: Any]).kj.model(type) 32 | } 33 | 34 | /// JSONObject -> Model 35 | func model(type: Convertible.Type) -> Convertible { 36 | return (base as [String: Any]).kj.model(type: type) 37 | } 38 | } 39 | 40 | public extension KJ where Base: NSDictionary { 41 | /// JSONObject -> Model 42 | func model(_ type: M.Type) -> M? { 43 | return (base as? [String: Any])?.kj.model(type) 44 | } 45 | 46 | /// JSONObject -> Model 47 | func model(type: Convertible.Type) -> Convertible? { 48 | return (base as? [String: Any])?.kj.model(type: type) 49 | } 50 | } 51 | 52 | extension Dictionary where Key == String { 53 | func kj_fastModel(_ type: Convertible.Type) -> Convertible { 54 | var model: Convertible 55 | if let ns = type as? NSObject.Type { 56 | model = ns.newConvertible() 57 | } else { 58 | model = type.init() 59 | } 60 | model.kj_convert(from: self) 61 | return model 62 | } 63 | 64 | func kj_value(for modelPropertyKey: ModelPropertyKey) -> Any? { 65 | if let key = modelPropertyKey as? String { 66 | return _value(stringKey: key) 67 | } 68 | 69 | let keyArray = modelPropertyKey as! [String] 70 | for key in keyArray { 71 | if let value = _value(stringKey: key) { 72 | return value 73 | } 74 | } 75 | return nil 76 | } 77 | 78 | private func _value(stringKey: String) -> Any? { 79 | if let value = self[stringKey] { 80 | return value 81 | } 82 | 83 | let subkeys = stringKey.split(separator: ".") 84 | var value: Any? = self 85 | for subKey in subkeys { 86 | if let dict = value as? [String: Any] { 87 | value = dict[String(subKey)] 88 | // when nil, end the for-loop 89 | if value == nil { return nil } 90 | } else if let array = value as? [Any] { 91 | guard let index = Int(subKey), 92 | case array.indices = index else { return nil } 93 | value = array[index] 94 | } else { 95 | break 96 | } 97 | } 98 | return value 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Extension/JSONSerialization+KJ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONSerialization+KJ.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/2. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension JSONSerialization { 12 | static func kj_JSON(_ string: String, _ type: T.Type) -> T? { 13 | return string.data(using: .utf8).flatMap { kj_JSON($0, type) } 14 | } 15 | 16 | static func kj_JSON(_ data: Data, _ type: T.Type) -> T? { 17 | return try? jsonObject(with: data, 18 | options: .allowFragments) as? T 19 | } 20 | 21 | static func kj_string(_ json: Any?, 22 | prettyPrinted: Bool = false) -> String? { 23 | guard let value = json.kj_value else { return nil } 24 | guard value is [Any] || value is [String: Any] else { 25 | return "\(value)" 26 | } 27 | guard let data = try? data(withJSONObject: value, 28 | options: prettyPrinted ? [.prettyPrinted] : []) 29 | else { return nil } 30 | return String(data: data, encoding: .utf8) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Extension/NSObject+KJ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+KJ.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/15. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSObject { 12 | static func newConvertible() -> Convertible { 13 | return self.init() as! Convertible 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Extension/Optional+KJ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Optional+KJ.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/5. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol OptionalValue { 12 | var kj_value: Any? { get } 13 | } 14 | 15 | extension Optional: OptionalValue { 16 | var kj_value: Any? { 17 | guard let v = self else { return nil } 18 | return (v as? OptionalValue)?.kj_value ?? v 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Extension/Pointer+KJ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pointer+KJ.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/1. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | extension UnsafePointer { 10 | var kj_raw: UnsafeRawPointer { 11 | return UnsafeRawPointer(self) 12 | } 13 | var kj_mutable: UnsafeMutablePointer { 14 | return UnsafeMutablePointer(mutating: self) 15 | } 16 | } 17 | 18 | extension UnsafeMutablePointer { 19 | var kj_raw: UnsafeMutableRawPointer { 20 | return UnsafeMutableRawPointer(self) 21 | } 22 | 23 | var kj_immutable: UnsafePointer { 24 | return UnsafePointer(self) 25 | } 26 | } 27 | 28 | extension UnsafeRawPointer { 29 | var kj_mutable: UnsafeMutableRawPointer { 30 | return UnsafeMutableRawPointer(mutating: self) 31 | } 32 | 33 | static func ~>(ptr: UnsafeRawPointer, type: T.Type) -> UnsafePointer { 34 | return ptr.assumingMemoryBound(to: type) 35 | } 36 | } 37 | 38 | extension UnsafeMutableRawPointer { 39 | var kj_immutable: UnsafeRawPointer { 40 | return UnsafeRawPointer(self) 41 | } 42 | 43 | func kj_set(_ value: Any, _ type: Any.Type) { 44 | return typeProxy(type)._set(value, self) 45 | } 46 | 47 | func kj_get(_ type: Any.Type) -> Any { 48 | return typeProxy(type)._get(self) 49 | } 50 | 51 | static func ~>(ptr: UnsafeMutableRawPointer, type: T.Type) -> UnsafeMutablePointer { 52 | return ptr.assumingMemoryBound(to: type) 53 | } 54 | } 55 | 56 | private extension TypeProxy { 57 | static func _set(_ value: Any, _ ptr: UnsafeMutableRawPointer) { 58 | (value as? Self).flatMap { (ptr ~> self).pointee = $0 } 59 | } 60 | 61 | static func _get(_ ptr: UnsafeMutableRawPointer) -> Any { 62 | return (ptr ~> self).pointee 63 | } 64 | } 65 | 66 | infix operator ~>> : MultiplicationPrecedence 67 | func ~>> (type1: T1, type2: T2.Type) -> T2 { 68 | return unsafeBitCast(type1, to: type2) 69 | } 70 | 71 | infix operator ~> : MultiplicationPrecedence 72 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Extension/String+KJ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+KJ.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/5. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String: KJCompatible {} 12 | extension NSString: KJCompatible {} 13 | 14 | public extension KJ where Base: ExpressibleByStringLiteral { 15 | /// from underline-cased to camel-cased 16 | /// 17 | /// e.g. from `my_test_name` to `myTestName` 18 | func camelCased() -> String { 19 | guard let str = base as? String else { return "" } 20 | var newStr = "" 21 | var upper = false 22 | for c in str { 23 | if c == "_" { 24 | upper = true 25 | continue 26 | } 27 | 28 | if upper, newStr.count > 0 { 29 | newStr += c.uppercased() 30 | } else { 31 | newStr.append(c) 32 | } 33 | upper = false 34 | } 35 | return newStr 36 | } 37 | 38 | /// from camel-cased to underline-cased 39 | /// 40 | /// e.g. from `myTestName` to `my_test_name` 41 | func underlineCased() -> String { 42 | guard let str = base as? String else { return "" } 43 | var newStr = "" 44 | for c in str { 45 | if c.isUppercase { 46 | newStr += "_" 47 | newStr += c.lowercased() 48 | } else { 49 | newStr.append(c) 50 | } 51 | } 52 | return newStr 53 | } 54 | 55 | /// JSONObject -> Model 56 | func model(_ type: M.Type) -> M? { 57 | return model(type: type) as? M 58 | } 59 | 60 | /// JSONObject -> Model 61 | func model(type: Convertible.Type) -> Convertible? { 62 | guard let string = base as? String else { return nil } 63 | return string.kj_fastModel(type) 64 | } 65 | 66 | /// JSONObjectArray -> ModelArray 67 | func modelArray(_ type: M.Type) -> [M] { 68 | return modelArray(type: type) as! [M] 69 | } 70 | 71 | /// JSONObjectArray -> ModelArray 72 | func modelArray(type: Convertible.Type) -> [Convertible] { 73 | guard let string = base as? String else { return [] } 74 | if let json = JSONSerialization.kj_JSON(string, [Any].self) { 75 | return json.kj.modelArray(type: type) 76 | } 77 | Logger.error("Failed to get JSON from JSONString.") 78 | return [] 79 | } 80 | } 81 | 82 | extension String { 83 | func kj_fastModel(_ type: Convertible.Type) -> Convertible? { 84 | if let json = JSONSerialization.kj_JSON(self, [String: Any].self) { 85 | return json.kj_fastModel(type) 86 | } 87 | Logger.error("Failed to get JSON from JSONString.") 88 | return nil 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Global/Coding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coding.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/22. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @discardableResult 12 | public func write(_ value: Any, 13 | to file: String, 14 | atomically: Bool = true, 15 | encoding: String.Encoding = .utf8) -> String? { 16 | return write(value, to: URL(fileURLWithPath: file), encoding: encoding) 17 | } 18 | 19 | @discardableResult 20 | public func write(_ value: Any, 21 | to URL: URL, 22 | atomically: Bool = true, 23 | encoding: String.Encoding = .utf8) -> String? { 24 | if URL.isFileURL { 25 | let dir = (URL.path as NSString).deletingLastPathComponent 26 | let mgr = FileManager.default 27 | if !mgr.fileExists(atPath: dir) { 28 | try? mgr.createDirectory(atPath: dir, 29 | withIntermediateDirectories: true, 30 | attributes: nil) 31 | } 32 | } 33 | let string = Values.JSONString(value) 34 | try? string?.write(to: URL, 35 | atomically: atomically, 36 | encoding: .utf8) 37 | return string 38 | } 39 | 40 | public func read(_ type: T.Type, 41 | from file: String, 42 | encoding: String.Encoding = .utf8) -> T? { 43 | return read(type, 44 | from: URL(fileURLWithPath: file), 45 | encoding: encoding) 46 | } 47 | 48 | public func read(_ type: T.Type, 49 | from URL: URL, 50 | encoding: String.Encoding = .utf8) -> T? { 51 | guard let data = try? Data(contentsOf: URL) else { return nil } 52 | 53 | var value = JSONSerialization.kj_JSON(data, Any.self) 54 | if value == nil { 55 | value = String(data: data, encoding: encoding) 56 | } 57 | return Values.value(value, T.self) as? T 58 | } 59 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Global/JSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSON.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/2. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | // MARK: Model -> JSON 10 | public func JSONObject(from model: M) -> [String: Any] { 11 | return model.kj_JSONObject() 12 | } 13 | 14 | public func JSONObjectArray(from models: [M]) -> [[String: Any]] { 15 | return models.kj.JSONObjectArray() 16 | } 17 | 18 | //public func JSONArray(from value: [Any]) -> [Any] { 19 | // return value.kj.JSONArray() 20 | //} 21 | // 22 | //public func JSON(from value: Any) -> Any? { 23 | // return Values.JSONValue(value) 24 | //} 25 | 26 | public func JSONString(from value: Any, 27 | prettyPrinted: Bool = false) -> String { 28 | return Values.JSONString(value, prettyPrinted: prettyPrinted) ?? "" 29 | } 30 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Global/Model.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Model.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/22. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - JSON -> Model 12 | public func model(from json: [String: Any], 13 | _ type: M.Type) -> M { 14 | return json.kj.model(type) 15 | } 16 | 17 | public func model(from json: [String: Any], 18 | type: Convertible.Type) -> Convertible { 19 | return json.kj.model(type: type) 20 | } 21 | 22 | public func model(from json: [NSString: Any], 23 | _ type: M.Type) -> M { 24 | return json.kj.model(type) 25 | } 26 | 27 | public func model(from json: [NSString: Any], 28 | type: Convertible.Type) -> Convertible { 29 | return json.kj.model(type: type) 30 | } 31 | 32 | public func model(from json: NSDictionary, 33 | _ type: M.Type) -> M? { 34 | return json.kj.model(type) 35 | } 36 | 37 | public func model(from json: NSDictionary, 38 | type: Convertible.Type) -> Convertible? { 39 | return json.kj.model(type: type) 40 | } 41 | 42 | public func model(from jsonString: String, 43 | _ type: M.Type) -> M? { 44 | return jsonString.kj.model(type) 45 | } 46 | 47 | public func model(from jsonString: String, 48 | type: Convertible.Type) -> Convertible? { 49 | return jsonString.kj.model(type: type) 50 | } 51 | 52 | public func model(from jsonString: NSString, 53 | _ type: M.Type) -> M? { 54 | return jsonString.kj.model(type) 55 | } 56 | 57 | public func model(from jsonString: NSString, 58 | type: Convertible.Type) -> Convertible? { 59 | return jsonString.kj.model(type: type) 60 | } 61 | 62 | public func model(from jsonData: Data, 63 | _ type: M.Type) -> M? { 64 | return jsonData.kj.model(type) 65 | } 66 | 67 | public func model(from jsonData: Data, 68 | type: Convertible.Type) -> Convertible? { 69 | return jsonData.kj.model(type: type) 70 | } 71 | 72 | public func model(from jsonData: NSData, 73 | _ type: M.Type) -> M? { 74 | return jsonData.kj.model(type) 75 | } 76 | 77 | public func model(from jsonData: NSData, 78 | type: Convertible.Type) -> Convertible? { 79 | return jsonData.kj.model(type: type) 80 | } 81 | 82 | public func modelArray(from json: [Any], 83 | _ type: M.Type) -> [M] { 84 | return json.kj.modelArray(type) 85 | } 86 | 87 | public func modelArray(from json: [Any], 88 | type: Convertible.Type) -> [Convertible] { 89 | return json.kj.modelArray(type: type) 90 | } 91 | 92 | public func modelArray(from json: NSArray, 93 | _ type: M.Type) -> [M] { 94 | return json.kj.modelArray(type) 95 | } 96 | 97 | public func modelArray(from json: NSArray, 98 | type: Convertible.Type) -> [Convertible] { 99 | return json.kj.modelArray(type: type) 100 | } 101 | 102 | public func modelArray(from jsonString: String, 103 | _ type: M.Type) -> [M] { 104 | return jsonString.kj.modelArray(type) 105 | } 106 | 107 | public func modelArray(from jsonString: String, 108 | type: Convertible.Type) -> [Convertible] { 109 | return jsonString.kj.modelArray(type: type) 110 | } 111 | 112 | public func modelArray(from jsonString: NSString, 113 | _ type: M.Type) -> [M] { 114 | return jsonString.kj.modelArray(type) 115 | } 116 | 117 | public func modelArray(from jsonString: NSString, 118 | type: Convertible.Type) -> [Convertible] { 119 | return jsonString.kj.modelArray(type: type) 120 | } 121 | 122 | public func modelArray(from jsonData: Data, 123 | _ type: M.Type) -> [M] { 124 | return jsonData.kj.modelArray(type) 125 | } 126 | 127 | public func modelArray(from jsonData: Data, 128 | type: Convertible.Type) -> [Convertible] { 129 | return jsonData.kj.modelArray(type: type) 130 | } 131 | 132 | public func modelArray(from jsonData: NSData, 133 | _ type: M.Type) -> [M] { 134 | return jsonData.kj.modelArray(type) 135 | } 136 | 137 | public func modelArray(from jsonData: NSData, 138 | type: Convertible.Type) -> [Convertible] { 139 | return jsonData.kj.modelArray(type: type) 140 | } 141 | 142 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Descriptor/ClassDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClassDescriptor.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct ClassDescriptor: ModelDescriptor { 10 | /// Flags describing the context, including its kind and format version 11 | let flags: ContextDescriptorFlags 12 | 13 | /// The parent context, or null if this is a top-level context. 14 | let parent: RelativeContextPointer 15 | 16 | /// The name of the type 17 | var name: RelativeDirectPointer 18 | 19 | /// A pointer to the metadata access function for this type 20 | let accessFunctionPtr: RelativeDirectPointer 21 | 22 | /// A pointer to the field descriptor for the type, if any 23 | var fields: RelativeDirectPointer 24 | 25 | /// The type of the superclass, expressed as a mangled type name that can refer to the generic arguments of the subclass type 26 | let superclassType: RelativeDirectPointer 27 | 28 | /// If this descriptor does not have a resilient superclass, this is the negative size of metadata objects of this class (in words) 29 | let metadataNegativeSizeInWords: UInt32 30 | 31 | /// If this descriptor does not have a resilient superclass, this is the positive size of metadata objects of this class (in words) 32 | let metadataPositiveSizeInWords: UInt32 33 | 34 | /// The number of additional members added by this class to the class metadata 35 | let numImmediateMembers: UInt32 36 | 37 | /// The number of stored properties in the class, not including its superclasses. If there is a field offset vector, this is its length. 38 | let numFields: UInt32 39 | 40 | /// The offset of the field offset vector for this class's stored properties in its metadata, in words. 0 means there is no field offset vector 41 | let fieldOffsetVectorOffset: FieldOffsetPointer 42 | 43 | let genericContextHeader: TargetTypeGenericContextDescriptorHeader 44 | } 45 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Descriptor/Descriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Descriptor.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | /// Must be struct, do not use class 10 | /// Descriptor for layout 11 | protocol Descriptor {} 12 | 13 | // MARK: - NominalDescriptor 14 | protocol NominalDescriptor: Descriptor { 15 | /// Flags describing the context, including its kind and format version 16 | var flags: ContextDescriptorFlags { get } 17 | 18 | /// The parent context, or null if this is a top-level context. 19 | var parent: RelativeContextPointer { get } 20 | 21 | /// The name of the type 22 | var name: RelativeDirectPointer { get set } 23 | 24 | /// A pointer to the metadata access function for this type 25 | var accessFunctionPtr: RelativeDirectPointer { get } 26 | 27 | /// A pointer to the field descriptor for the type, if any 28 | var fields: RelativeDirectPointer { get set } 29 | 30 | associatedtype OffsetType: BinaryInteger 31 | var fieldOffsetVectorOffset: FieldOffsetPointer { get } 32 | 33 | /// generic info 34 | var genericContextHeader: TargetTypeGenericContextDescriptorHeader { get } 35 | } 36 | 37 | extension NominalDescriptor { 38 | var isGeneric: Bool { return (flags.value & 0x80) != 0 } 39 | var genericTypesCount: Int { return Int(genericContextHeader.base.numberOfParams) } 40 | } 41 | 42 | // MARK: - ModelDescriptor 43 | protocol ModelDescriptor: NominalDescriptor { 44 | var numFields: UInt32 { get } 45 | } 46 | 47 | extension ModelDescriptor { 48 | func fieldOffsets(_ type: Any.Type) -> [Int] { 49 | let ptr = ((type ~>> UnsafePointer.self) + Int(fieldOffsetVectorOffset.offset)) 50 | .kj_raw ~> OffsetType.self 51 | return (0.. { 65 | var relativeOffset: Int32 66 | 67 | mutating func advanced() -> UnsafeMutablePointer { 68 | let offset = relativeOffset 69 | return withUnsafeMutablePointer(to: &self) { 70 | ($0.kj_raw + Int(offset)) ~> Pointee.self 71 | } 72 | } 73 | } 74 | 75 | struct FieldOffsetPointer { 76 | let offset: UInt32 77 | } 78 | 79 | struct MetadataResponse {} 80 | 81 | struct TargetTypeGenericContextDescriptorHeader { 82 | var instantiationCache: Int32 83 | var defaultInstantiationPattern: Int32 84 | var base: TargetGenericContextDescriptorHeader 85 | } 86 | 87 | struct TargetGenericContextDescriptorHeader { 88 | var numberOfParams: UInt16 89 | var numberOfRequirements: UInt16 90 | var numberOfKeyArguments: UInt16 91 | var numberOfExtraArguments: UInt16 92 | } 93 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Descriptor/EnumDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnumDescriptor.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct EnumDescriptor: NominalDescriptor { 10 | /// Flags describing the context, including its kind and format version 11 | let flags: ContextDescriptorFlags 12 | 13 | /// The parent context, or null if this is a top-level context. 14 | let parent: RelativeContextPointer 15 | 16 | /// The name of the type 17 | var name: RelativeDirectPointer 18 | 19 | /// A pointer to the metadata access function for this type 20 | let accessFunctionPtr: RelativeDirectPointer 21 | 22 | /// A pointer to the field descriptor for the type, if any 23 | var fields: RelativeDirectPointer 24 | 25 | /// The number of non-empty cases in the enum are in the low 24 bits; the offset of the payload size in the metadata record in words, if any, is stored in the high 8 bits 26 | let numPayloadCasesAndPayloadSizeOffset: UInt32 27 | 28 | /// The number of empty cases in the enum 29 | let numEmptyCases: UInt32 30 | 31 | let fieldOffsetVectorOffset: FieldOffsetPointer 32 | 33 | let genericContextHeader: TargetTypeGenericContextDescriptorHeader 34 | } 35 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Descriptor/FieldDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldDescriptor.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct FieldRecord { 12 | let flags: Int32 13 | var _mangledTypeName: RelativeDirectPointer 14 | var _fieldName: RelativeDirectPointer 15 | var isVar: Bool { return (flags & 0x2) == 0x2 } 16 | mutating func fieldName() -> String { return String(cString: _fieldName.advanced()) } 17 | mutating func mangledTypeName() -> String { return String(cString: _mangledTypeName.advanced()) } 18 | 19 | mutating func type(_ genericContext: UnsafeRawPointer?, 20 | _ genericArguments: UnsafeRawPointer?) -> Any.Type { 21 | let name = _mangledTypeName.advanced() 22 | return _getTypeByMangledNameInContext( 23 | name, 24 | nameLength(name), 25 | genericContext, 26 | genericArguments 27 | )! 28 | } 29 | 30 | func nameLength(_ begin: UnsafeRawPointer) -> UInt { 31 | var end = begin 32 | let size = MemoryLayout.size 33 | while true { 34 | let cur = end.load(as: UInt8.self) 35 | if cur == 0 { break } 36 | end += 1 37 | if cur <= 0x17 { 38 | end += 4 39 | } else if cur <= 0x1F { 40 | end += size 41 | } 42 | } 43 | return UInt(end - begin) 44 | } 45 | } 46 | 47 | struct FieldDescriptor { 48 | let mangledTypeName: RelativeDirectPointer 49 | let superclass: RelativeDirectPointer 50 | let _kind : UInt16 51 | let fieldRecordSize : UInt16 52 | let numFields : UInt32 53 | var fieldRecords: FieldList 54 | var kind: FieldDescriptorKind { return FieldDescriptorKind(rawValue: _kind)! } 55 | } 56 | 57 | enum FieldDescriptorKind: UInt16 { 58 | case `struct` 59 | case `class` 60 | case `enum` 61 | case multiPayloadEnum 62 | case `protocol` 63 | case classProtocol 64 | case objCProtocol 65 | case objCClass 66 | } 67 | 68 | @_silgen_name("swift_getTypeByMangledNameInContext") 69 | private func _getTypeByMangledNameInContext( 70 | _ name: UnsafePointer, 71 | _ nameLength: UInt, 72 | _ genericContext: UnsafeRawPointer?, 73 | _ genericArguments: UnsafeRawPointer?) 74 | -> Any.Type? 75 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Descriptor/StructDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StructDescriptor.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct StructDescriptor: ModelDescriptor { 10 | /// Flags describing the context, including its kind and format version 11 | let flags: ContextDescriptorFlags 12 | 13 | /// The parent context, or null if this is a top-level context. 14 | let parent: RelativeContextPointer 15 | 16 | /// The name of the type 17 | var name: RelativeDirectPointer 18 | 19 | /// A pointer to the metadata access function for this type 20 | let accessFunctionPtr: RelativeDirectPointer 21 | 22 | /// A pointer to the field descriptor for the type, if any 23 | var fields: RelativeDirectPointer 24 | 25 | /// The number of stored properties in the struct. If there is a field offset vector, this is its length 26 | let numFields: UInt32 27 | 28 | /// The offset of the field offset vector for this struct's stored properties in its metadata, if any. 0 means there is no field offset vector 29 | let fieldOffsetVectorOffset: FieldOffsetPointer 30 | 31 | let genericContextHeader: TargetTypeGenericContextDescriptorHeader 32 | } 33 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/LICENSE: -------------------------------------------------------------------------------- 1 | ## Type, Layout, Descriptor 2 | 3 | MIT License 4 | 5 | Copyright (c) 2017 Wesley Wickwire 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Layout/BaseLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseLayout.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/2. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct BaseLayout: Layout { 10 | let kind: UnsafeRawPointer 11 | } 12 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Layout/ClassLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClassLayout.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct ClassLayout: ModelLayout { 10 | let kind: UnsafeRawPointer 11 | let superclass: Any.Type 12 | 13 | /// The cache data is used for certain dynamic lookups; it is owned by the runtime and generally needs to interoperate with Objective-C's use 14 | let runtimeReserved0: UInt 15 | let runtimeReserved1: UInt 16 | 17 | /// The data pointer is used for out-of-line metadata and is generally opaque, except that the compiler sets the low bit in order to indicate that this is a Swift metatype and therefore that the type metadata header is present 18 | let rodata: UInt 19 | 20 | /// Swift-specific class flags 21 | let flags: UInt32 22 | 23 | /// The address point of instances of this type 24 | let instanceAddressPoint: UInt32 25 | 26 | /// The required size of instances of this type. 'InstanceAddressPoint' bytes go before the address point; 'InstanceSize - InstanceAddressPoint' bytes go after it 27 | let instanceSize: UInt32 28 | 29 | /// The alignment mask of the address point of instances of this type 30 | let instanceAlignMask: UInt16 31 | 32 | /// Reserved for runtime use 33 | let reserved: UInt16 34 | 35 | /// The total size of the class object, including prefix and suffix extents 36 | let classSize: UInt32 37 | 38 | /// The offset of the address point within the class object 39 | let classAddressPoint: UInt32 40 | 41 | // Description is by far the most likely field for a client to try to access directly, so we force access to go through accessors 42 | /// An out-of-line Swift-specific description of the type, or null if this is an artificial subclass. We currently provide no supported mechanism for making a non-artificial subclass dynamically 43 | var description: UnsafeMutablePointer 44 | 45 | /// A function for destroying instance variables, used to clean up after an early return from a constructor. If null, no clean up will be performed and all ivars must be trivial 46 | let iVarDestroyer: UnsafeRawPointer 47 | 48 | var genericTypeOffset: Int { 49 | let descriptor = description.pointee 50 | // don't have resilient superclass 51 | if (0x4000 & flags) == 0 { 52 | return (flags & 0x800) == 0 53 | ? Int(descriptor.metadataPositiveSizeInWords - descriptor.numImmediateMembers) 54 | : -Int(descriptor.metadataNegativeSizeInWords) 55 | } 56 | return GenenicTypeOffset.wrong 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Layout/EnumLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnumLayout.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct EnumLayout: NominalLayout { 10 | let kind: UnsafeRawPointer 11 | /// An out-of-line description of the type 12 | var description: UnsafeMutablePointer 13 | } 14 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Layout/FieldList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldList.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/1. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct FieldList { 10 | private let item: Item 11 | mutating func ptr(_ index: Int) -> UnsafeMutablePointer { 12 | return withUnsafeMutablePointer(to: &self) { 13 | ($0 + index).kj_raw ~> Item.self 14 | } 15 | } 16 | 17 | mutating func item(_ index: Int) -> Item { 18 | return withUnsafeMutablePointer(to: &self) { 19 | $0[index].item 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Layout/FunctionLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionLayout.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/1. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct FunctionLayout: Layout { 10 | let kind: UnsafeRawPointer 11 | var flags: Int 12 | var parameters: FieldList 13 | } 14 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Layout/Layout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Layout.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | /// Must be struct, do not use class 10 | /// Memory layout for metadata 11 | protocol Layout { 12 | // Only valid for non-class metadata 13 | var kind: UnsafeRawPointer { get } 14 | } 15 | 16 | protocol NominalLayout: Layout { 17 | associatedtype DescriptorType: NominalDescriptor 18 | var description: UnsafeMutablePointer { get } 19 | var genericTypeOffset: Int { get } 20 | } 21 | 22 | extension NominalLayout { 23 | var genericTypeOffset: Int { return 2 } 24 | } 25 | 26 | protocol ModelLayout: NominalLayout where DescriptorType: ModelDescriptor {} 27 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Layout/ProtocolLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProtocolLayout.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/1. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct ProtocolLayout: Layout { 10 | let kind: UnsafeRawPointer 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Layout/StructLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StructLayout.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct StructLayout: ModelLayout { 10 | let kind: UnsafeRawPointer 11 | /// An out-of-line description of the type 12 | var description: UnsafeMutablePointer 13 | } 14 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Layout/TupleLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TupleLayout.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/1. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | struct TupleLayout: Layout { 10 | let kind: UnsafeRawPointer 11 | let numElements: Int 12 | let labels: UnsafeMutablePointer 13 | var elements: FieldList 14 | } 15 | 16 | struct TupleElement { 17 | let type: Any.Type 18 | let offset: Int 19 | } 20 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Metadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Metadata.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/4. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Metadata { 12 | private static let typeLock = NSRecursiveLock() 13 | private static var types = [TypeKey: BaseType]() 14 | 15 | public static func type(_ type: Any.Type) -> BaseType? { 16 | typeLock.lock() 17 | defer { typeLock.unlock() } 18 | 19 | // get from cache 20 | let key = typeKey(type) 21 | if let mt = types[key] { return mt } 22 | 23 | // name 24 | let name = String(describing: type) 25 | if name == "Swift._SwiftObject" 26 | || name == "NSObject" 27 | || name == "_TtCs12_SwiftObject" { return nil } 28 | 29 | // type judge 30 | var mtt: BaseType.Type 31 | let kind = Kind(type) 32 | switch kind { 33 | case .class: mtt = ClassType.self 34 | case .struct: mtt = StructType.self 35 | case .enum: mtt = EnumType.self 36 | case .optional: mtt = OptionalType.self 37 | case .objCClassWrapper: mtt = ObjCClassType.self 38 | case .foreignClass: mtt = ForeignClassType.self 39 | case .tuple: mtt = TupleType.self 40 | case .function: mtt = FunctionType.self 41 | case .existential: mtt = ProtocolType.self 42 | case .metatype: mtt = MetaType.self 43 | default: mtt = BaseType.self 44 | } 45 | 46 | // ceate and put it into cache 47 | let mt = mtt.init(name: name, type: type, kind: kind) 48 | types[key] = mt 49 | return mt 50 | } 51 | 52 | public static func type(_ obj: Any) -> BaseType? { 53 | return type(Swift.type(of: obj)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Property.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Property.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/30. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | /// Info for stored property(存储属性的相关信息) 10 | public class Property: CustomStringConvertible { 11 | public let name: String 12 | public let type: Any.Type 13 | public private(set) lazy var dataType: Any.Type = type~! 14 | public let isVar: Bool 15 | public let offset: Int 16 | public let ownerType: Any.Type 17 | 18 | init(name: String, type: Any.Type, 19 | isVar: Bool, offset: Int, 20 | ownerType: Any.Type) { 21 | self.name = name 22 | self.type = type 23 | self.isVar = isVar 24 | self.offset = offset 25 | self.ownerType = ownerType 26 | } 27 | 28 | func set(_ value: Any, for model: UnsafeMutableRawPointer) { 29 | (model + offset).kj_set(value, type) 30 | } 31 | 32 | func get(from model: UnsafeMutableRawPointer) -> Any { 33 | return (model + offset).kj_get(type) 34 | } 35 | 36 | public var description: String { 37 | return "\(name) { type = \(type), isVar = \(isVar), offset = \(offset), ownerType = \(ownerType) }" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/BaseType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/30. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | /// Info for type(类型的相关信息) 10 | public class BaseType: Type, CustomStringConvertible { 11 | public let name: String 12 | public let type: Any.Type 13 | public let kind: Kind 14 | public private(set) var module: String = "" 15 | public private(set) var isSystemModule: Bool = false 16 | 17 | required init(name: String, type: Any.Type, kind: Kind) { 18 | self.name = name 19 | self.type = type 20 | self.kind = kind 21 | 22 | build() 23 | } 24 | 25 | func build() { 26 | switch kind { 27 | case .optional, .struct, .class, .existential, 28 | .enum, .objCClassWrapper, .foreignClass: 29 | let name = String(reflecting: type) 30 | guard let end = name.firstIndex(of: ".") else { break } 31 | module = String(name[name.startIndex..! 11 | public private(set) var `super`: ClassType? 12 | // public private(set) var isPureSwiftClass: Bool = false 13 | 14 | override func build() { 15 | super.build() 16 | 17 | layout = builtLayout() 18 | genericTypes = builtGenericTypes() 19 | properties = builtProperties() 20 | 21 | `super` = Metadata.type(layout.pointee.superclass) as? ClassType 22 | if let superProperties = `super`?.properties { 23 | properties = properties ?? [] 24 | properties!.insert(contentsOf: superProperties, at: 0) 25 | } 26 | 27 | /// Not sure 28 | // isPureSwiftClass = (layout.pointee.rodata ~>> UnsafePointer.self).pointee > 0 29 | } 30 | 31 | override public var description: String { 32 | return "\(name) { kind = \(kind), properties = \(properties ?? []), genericTypes = \(genericTypes ?? []), super = \(`super` != nil ? String(describing: `super`!) : "nil") }" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/EnumType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnumType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | public class EnumType: BaseType, NominalType, LayoutType { 10 | private(set) var layout: UnsafeMutablePointer! 11 | public private(set) var genericTypes: [Any.Type]? 12 | public private(set) var cases: [String]? 13 | 14 | override func build() { 15 | super.build() 16 | 17 | layout = builtLayout() 18 | genericTypes = builtGenericTypes() 19 | 20 | let description = layout.pointee.description 21 | let count = Int(description.pointee.numEmptyCases) 22 | guard count > 0 else { return } 23 | let descriptor = description.pointee.fields.advanced() 24 | cases = (0..! 12 | 13 | override func build() { 14 | super.build() 15 | 16 | layout = builtLayout() 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/FunctionType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/1. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | public class FunctionType: BaseType, LayoutType { 10 | private(set) var layout: UnsafeMutablePointer! 11 | public private(set) var `throws`: Bool = false 12 | public private(set) var returnType: Any.Type = Any.self 13 | public private(set) var argumentTypes: [Any.Type]? 14 | 15 | override func build() { 16 | super.build() 17 | 18 | layout = builtLayout() 19 | 20 | `throws` = (layout.pointee.flags & 0x01000000) != 0 21 | returnType = layout.pointee.parameters.item(0) 22 | 23 | let argumentsCount = layout.pointee.flags & 0x00FFFFFF 24 | guard argumentsCount > 0 else { return } 25 | var arr = [Any.Type]() 26 | for i in 1...argumentsCount { 27 | arr.append(layout.pointee.parameters.item(i)) 28 | } 29 | argumentTypes = arr 30 | } 31 | 32 | override public var description: String { 33 | return "\(name) { kind = \(kind), argumentTypes = \(argumentTypes ?? []), returnType = \(returnType), throws = \(`throws`) }" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/Kind.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Kind.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/30. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | public enum Kind { 10 | // MARK: - cases 11 | 12 | case `class` 13 | /// e.g. Int、String 14 | case `struct` 15 | /// e.g. MemoryLayout 16 | case `enum` 17 | /// e.g. Optional、T? 18 | case optional 19 | /// Such as a Core Foundation class, e.g. CFArray 20 | case foreignClass 21 | /// A type whose value is not exposed in the metadata system 22 | case opaque 23 | case tuple 24 | /// A monomorphic function, e.g. () -> Void 25 | case function 26 | /// An existential type, e.g. protocol 27 | case existential 28 | /// A metatype 29 | case metatype 30 | /// An ObjC class wrapper, e.g. NSString 31 | case objCClassWrapper 32 | /// An existential metatype 33 | case existentialMetatype 34 | /// A heap-allocated local variable using statically-generated metadata 35 | case heapLocalVariable 36 | case heapArray 37 | /// A heap-allocated local variable using runtime-instantiated metadata. 38 | case heapGenericLocalVariable 39 | /// A native error object. 40 | case errorObject 41 | 42 | // MARK: - Some flags 43 | /// Non-type metadata kinds have this bit set 44 | private static let nonType: UInt = 0x400 45 | /// Non-heap metadata kinds have this bit set 46 | private static let nonHeap: UInt = 0x200 47 | /* 48 | The above two flags are negative because the "class" kind has to be zero, and class metadata is both type and heap metadata. 49 | */ 50 | /// Runtime-private metadata has this bit set. The compiler must not statically generate metadata objects with these kinds, and external tools should not rely on the stability of these values or the precise binary layout of their associated data structures 51 | private static let runtimePrivate: UInt = 0x100 52 | private static let runtimePrivate_nonHeap = runtimePrivate | nonHeap 53 | private static let runtimePrivate_nonType = runtimePrivate | nonType 54 | 55 | // MARK: - initialization 56 | 57 | /// 获得Any.Type对应的MetadataKind 58 | init(_ type: Any.Type) { 59 | let kind = (type ~>> UnsafePointer.self).pointee 60 | 61 | switch kind { 62 | case 0 | Kind.nonHeap, 1: self = .struct 63 | case 1 | Kind.nonHeap, 2: self = .enum 64 | case 2 | Kind.nonHeap, 3: self = .optional 65 | case 3 | Kind.nonHeap: self = .foreignClass 66 | 67 | case 0 | Kind.runtimePrivate_nonHeap, 8: self = .opaque 68 | case 1 | Kind.runtimePrivate_nonHeap, 9: self = .tuple 69 | case 2 | Kind.runtimePrivate_nonHeap, 10: self = .function 70 | case 3 | Kind.runtimePrivate_nonHeap, 12: self = .existential 71 | case 4 | Kind.runtimePrivate_nonHeap, 13: self = .metatype 72 | case 5 | Kind.runtimePrivate_nonHeap, 14: self = .objCClassWrapper 73 | case 6 | Kind.runtimePrivate_nonHeap, 15: self = .existentialMetatype 74 | 75 | case 0 | Kind.nonType, 64: self = .heapLocalVariable 76 | case 0 | Kind.runtimePrivate_nonType: self = .heapGenericLocalVariable 77 | case 1 | Kind.runtimePrivate_nonType: self = .errorObject 78 | 79 | case 65: self = .heapArray 80 | 81 | case 0: fallthrough 82 | default: self = .class 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/MetaType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetaType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/2. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | public class MetaType: BaseType, LayoutType { 10 | private(set) var layout: UnsafeMutablePointer! 11 | 12 | override func build() { 13 | super.build() 14 | 15 | layout = builtLayout() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/ModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/13. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class ModelType: BaseType { 12 | public internal(set) var properties: [Property]? 13 | public internal(set) var genericTypes: [Any.Type]? 14 | private var modelKeysLock = DispatchSemaphore(value: 1) 15 | private var modelKeys: [String: ModelPropertyKey] = [:] 16 | private var jsonKeysLock = DispatchSemaphore(value: 1) 17 | private var jsonKeys: [String: String] = [:] 18 | 19 | func modelKey(from propertyName: String, 20 | _ createdKey: @autoclosure () -> ModelPropertyKey) -> ModelPropertyKey { 21 | modelKeysLock.wait() 22 | defer { modelKeysLock.signal() } 23 | if let key = modelKeys[propertyName] { return key } 24 | 25 | var resultKey = createdKey() 26 | if let stringKey = resultKey as? String { 27 | if stringKey != propertyName { 28 | resultKey = [stringKey, propertyName] 29 | } 30 | } else if var arrayKey = resultKey as? [String] { 31 | if arrayKey.count == 0 { 32 | resultKey = propertyName 33 | } else if arrayKey.last != propertyName { 34 | arrayKey.append(propertyName) 35 | resultKey = arrayKey 36 | } 37 | } 38 | modelKeys[propertyName] = resultKey; 39 | return resultKey 40 | } 41 | 42 | func JSONKey(from propertyName: String, 43 | _ createdKey: @autoclosure () -> String) -> String { 44 | jsonKeysLock.wait() 45 | defer { jsonKeysLock.signal() } 46 | if let key = jsonKeys[propertyName] { return key } 47 | 48 | let resultKey = createdKey() 49 | jsonKeys[propertyName] = resultKey 50 | return resultKey 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/ObjCClassType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjCClassType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/2. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | /// e.g. NSString 10 | public class ObjCClassType: BaseType, LayoutType { 11 | private(set) var layout: UnsafeMutablePointer! 12 | 13 | override func build() { 14 | super.build() 15 | 16 | layout = builtLayout() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/OptionalType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionalType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/2. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | /// Optional metadata share the same basic layout as enum metadata 10 | public class OptionalType: EnumType { 11 | public private(set) var wrapType: Any.Type = Any.self 12 | 13 | override func build() { 14 | super.build() 15 | 16 | var wt: BaseType! = self 17 | while wt.kind == .optional { 18 | wt = Metadata.type((wt as! OptionalType).genericTypes![0]) 19 | } 20 | wrapType = wt.type 21 | } 22 | 23 | override public var description: String { 24 | return "\(name) { kind = \(kind), wrapType = \(wrapType) }" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/ProtocolType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProtocolType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/1. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | public class ProtocolType: BaseType, LayoutType { 10 | private(set) var layout: UnsafeMutablePointer! 11 | 12 | override func build() { 13 | super.build() 14 | 15 | layout = builtLayout() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/StructType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StructType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/7/31. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | public class StructType: ModelType, PropertyType, LayoutType { 10 | private(set) var layout: UnsafeMutablePointer! 11 | 12 | override func build() { 13 | super.build() 14 | 15 | layout = builtLayout() 16 | properties = builtProperties() 17 | genericTypes = builtGenericTypes() 18 | } 19 | 20 | override public var description: String { 21 | return "\(name) { kind = \(kind), properties = \(properties ?? []), genericTypes = \(genericTypes ?? []) }" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/KakaJSON/Metadata/Type/TupleType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TupleType.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2019/8/1. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | public class TupleType: BaseType, LayoutType { 10 | private(set) var layout: UnsafeMutablePointer! 11 | public private(set) var properties: [Property]! 12 | 13 | override func build() { 14 | super.build() 15 | 16 | layout = builtLayout() 17 | 18 | let elementsCount = layout.pointee.numElements 19 | guard elementsCount > 0 else { return } 20 | var names: [String] 21 | if layout.pointee.labels ~>> Int.self == 0 { 22 | names = Array(repeating: "", count: elementsCount) 23 | } else { 24 | names = String(cString: layout.pointee.labels).components(separatedBy: " ") 25 | } 26 | properties = (0..! { get } 22 | } 23 | 24 | extension LayoutType { 25 | func builtLayout() -> UnsafeMutablePointer { 26 | return type ~>> UnsafeMutablePointer.self 27 | } 28 | } 29 | 30 | // MARK: - NominalType 31 | protocol NominalType: Type { 32 | var genericTypes: [Any.Type]? { get } 33 | } 34 | 35 | extension NominalType where Self: LayoutType, InnerLayout: NominalLayout { 36 | func genenicTypesPtr() -> UnsafeMutablePointer> { 37 | // get the offset 38 | let offset = layout.pointee.genericTypeOffset * MemoryLayout.size 39 | 40 | // pointer to generic types 41 | return ((type ~>> UnsafeMutableRawPointer.self) + offset) ~> FieldList.self 42 | } 43 | 44 | func builtGenericTypes() -> [Any.Type]? { 45 | // generic judge 46 | let description = layout.pointee.description 47 | if !description.pointee.isGeneric { return nil } 48 | let typesCount = description.pointee.genericTypesCount 49 | if typesCount <= 0 { return nil } 50 | 51 | // pointer to generic types 52 | if layout.pointee.genericTypeOffset == GenenicTypeOffset.wrong { return nil } 53 | let ptr = genenicTypesPtr() 54 | return (0.. [Property]? { 69 | let description = layout.pointee.description 70 | let offsets = description.pointee.fieldOffsets(type) 71 | if offsets.isEmpty { return nil } 72 | let fieldDescriptorPtr = description.pointee.fields.advanced() 73 | let ptr = genenicTypesPtr() 74 | return (0.. Property in 75 | let recordPtr = fieldDescriptorPtr.pointee.fieldRecords.ptr(i) 76 | return Property(name: recordPtr.pointee.fieldName(), 77 | type: recordPtr.pointee.type(layout.pointee.description, ptr), 78 | isVar: recordPtr.pointee.isVar, 79 | offset: offsets[i], 80 | ownerType: type) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/JSON_To_Model/JTM_01_Basic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTM_01_Basic.swift 3 | // KakaJSONTest 4 | // 5 | // Created by MJ Lee on 2019/8/7. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | class JTM_01_Basic: XCTestCase { 13 | struct Cat: Convertible { 14 | var weight: Double = 0.0 15 | var name: String = "" 16 | } 17 | 18 | // MARK: - Generic Type 19 | func testGeneric() { 20 | let name = "Miaomiao" 21 | let weight = 6.66 22 | 23 | // json can also be NSDictionary\NSMutableDictionary 24 | let json: [String: Any] = [ 25 | "weight": weight, 26 | "name": name 27 | ] 28 | 29 | let cat = json.kj.model(Cat.self) 30 | XCTAssert(cat.name == name) 31 | XCTAssert(cat.weight == weight) 32 | } 33 | 34 | // MARK: - Any.Type 35 | func testAny() { 36 | let name: String = "Miaomiao" 37 | let weight: Double = 6.66 38 | 39 | let json: [String: Any] = [ 40 | "weight": weight, 41 | "name": name 42 | ] 43 | 44 | let type: Convertible.Type = Cat.self 45 | let cat = json.kj.model(type: type) as? Cat 46 | XCTAssert(cat?.name == name) 47 | XCTAssert(cat?.weight == weight) 48 | } 49 | 50 | // MARK: - json String 51 | func testJSONString() { 52 | let name = "Miaomiao" 53 | let weight = 6.66 54 | 55 | // jsonString can also be NSString\NSMutableString 56 | let jsonString: String = """ 57 | { 58 | "name": "\(name)", 59 | "weight": \(weight) 60 | } 61 | """ 62 | 63 | let cat = jsonString.kj.model(Cat.self) 64 | XCTAssert(cat?.name == name) 65 | XCTAssert(cat?.weight == weight) 66 | } 67 | 68 | // MARK: - json data 69 | func testJSONData() { 70 | let name = "Miaomiao" 71 | let weight = 6.66 72 | 73 | // jsonData can also be NSData\NSMutableData 74 | let jsonData = """ 75 | { 76 | "name": "\(name)", 77 | "weight": \(weight) 78 | } 79 | """.data(using: .utf8)! 80 | 81 | let cat = jsonData.kj.model(Cat.self) 82 | XCTAssert(cat?.name == name) 83 | XCTAssert(cat?.weight == weight) 84 | } 85 | 86 | // MARK: - NSNull 87 | func testNSNull() { 88 | struct Cat: Convertible { 89 | var weight: Double = 0.0 90 | var name: String = "xx" 91 | var data: NSNull? 92 | } 93 | 94 | let json: [String: Any] = [ 95 | "name": NSNull(), 96 | "weight": 6.6, 97 | "data": NSNull() 98 | ] 99 | 100 | let cat = json.kj.model(Cat.self) 101 | // convert failed, keep default value 102 | XCTAssert(cat.name == "xx") 103 | XCTAssert(cat.weight == 6.6) 104 | XCTAssert(cat.data == NSNull()) 105 | } 106 | 107 | // MARK: - let 108 | func testLet() { 109 | struct Cat: Convertible { 110 | // let of integer type is very restricted in release mode 111 | // please user `private(set) var` instead of `let` 112 | private(set) var weight: Double = 0.0 113 | let name: String = "" 114 | } 115 | let name: String = "Miaomiao" 116 | let weight: Double = 6.66 117 | 118 | let json: [String: Any] = [ 119 | "weight": weight, 120 | "name": name 121 | ] 122 | 123 | let cat = json.kj.model(Cat.self) 124 | XCTAssert(cat.name == name) 125 | XCTAssert(cat.weight == weight) 126 | } 127 | 128 | // MARK: - Class Type 129 | func testClass() { 130 | class Person: Convertible { 131 | var name: String = "" 132 | var age: Int = 0 133 | required init() {} 134 | } 135 | 136 | class Student: Person { 137 | var score: Int = 0 138 | var no: String = "" 139 | } 140 | 141 | let name = "jack" 142 | let age = 18 143 | let score = 98 144 | let no = "9527" 145 | 146 | let json: [String: Any] = [ 147 | "name": name, 148 | "age": age, 149 | "score": score, 150 | "no": no 151 | ] 152 | 153 | let student = json.kj.model(Student.self) 154 | XCTAssert(student.name == name) 155 | XCTAssert(student.age == age) 156 | XCTAssert(student.score == score) 157 | XCTAssert(student.no == no) 158 | } 159 | 160 | // MARK: - NSObject Class Type 161 | func testNSObjectClass() { 162 | class Person: NSObject, Convertible { 163 | var name: String = "" 164 | var age: Int = 0 165 | required override init() {} 166 | } 167 | 168 | class Student: Person { 169 | var score: Int = 0 170 | var no: String = "" 171 | } 172 | 173 | let name = "jack" 174 | let age = 18 175 | let score = 98 176 | let no = "9527" 177 | 178 | let json: [String: Any] = [ 179 | "name": name, 180 | "age": age, 181 | "score": score, 182 | "no": no 183 | ] 184 | 185 | let student = json.kj.model(Student.self) 186 | XCTAssert(student.name == name) 187 | XCTAssert(student.age == age) 188 | XCTAssert(student.score == score) 189 | XCTAssert(student.no == no) 190 | } 191 | 192 | // MARK: - Convert 193 | func testConvert() { 194 | let name = "Miaomiao" 195 | let weight = 6.66 196 | 197 | let json: [String: Any] = [ 198 | "weight": weight, 199 | "name": name 200 | ] 201 | 202 | var cat = Cat() 203 | cat.kj_m.convert(from: json) 204 | XCTAssert(cat.name == name) 205 | XCTAssert(cat.weight == weight) 206 | 207 | let JSONString = """ 208 | { 209 | "weight": 6.66, 210 | "name": "Miaomiao" 211 | } 212 | """ 213 | 214 | cat.kj_m.convert(from: JSONString) 215 | XCTAssert(cat.name == name) 216 | XCTAssert(cat.weight == weight) 217 | } 218 | 219 | func testCallback1() { 220 | struct Car: Convertible { 221 | var name: String = "" 222 | var age: Int = 0 223 | 224 | mutating func kj_willConvertToModel(from json: [String: Any]) { 225 | // print("Car - kj_willConvertToModel") 226 | } 227 | 228 | mutating func kj_didConvertToModel(from json: [String: Any]) { 229 | // print("Car - kj_didConvertToModel") 230 | } 231 | } 232 | 233 | let name = "Benz" 234 | let age = 100 235 | let car = ["name": name, "age": age].kj.model(Car.self) 236 | // Car - kj_willConvertToModel 237 | // Car - kj_didConvertToModel 238 | XCTAssert(car.name == name) 239 | XCTAssert(car.age == age) 240 | } 241 | 242 | func testCallback2() { 243 | class Person: Convertible { 244 | var name: String = "" 245 | var age: Int = 0 246 | required init() {} 247 | 248 | func kj_willConvertToModel(from json: [String: Any]) { 249 | // print("Person - kj_willConvertToModel") 250 | } 251 | 252 | func kj_didConvertToModel(from json: [String: Any]) { 253 | // print("Person - kj_didConvertToModel") 254 | } 255 | } 256 | 257 | class Student: Person { 258 | var score: Int = 0 259 | 260 | override func kj_willConvertToModel(from json: [String: Any]) { 261 | // call super's implementation if necessary 262 | super.kj_willConvertToModel(from: json) 263 | 264 | // print("Student - kj_willConvertToModel") 265 | } 266 | 267 | override func kj_didConvertToModel(from json: [String: Any]) { 268 | // call super's implementation if necessary 269 | super.kj_didConvertToModel(from: json) 270 | 271 | // print("Student - kj_didConvertToModel") 272 | } 273 | } 274 | 275 | let name = "jack" 276 | let age = 10 277 | let score = 100 278 | let student = ["name": name, "age": age, "score": score].kj.model(Student.self) 279 | // Person - kj_willConvertToModel 280 | // Student - kj_willConvertToModel 281 | // Person - kj_didConvertToModel 282 | // Student - kj_didConvertToModel 283 | XCTAssert(student.name == name) 284 | XCTAssert(student.age == age) 285 | XCTAssert(student.score == score) 286 | } 287 | 288 | func testOCModel() { 289 | // Not support model extends pure oc model 290 | final class Person: OCModel, Convertible { 291 | var name: String = "" 292 | var age: Int = 0 293 | } 294 | 295 | /* 296 | let name = "Jack" 297 | let age = 20 298 | let no = 1 299 | let json: [String: Any] = [ 300 | "no": no, 301 | "name": name, 302 | "age": age 303 | ] 304 | 305 | 306 | let person = json.kj.model(Person.self) 307 | XCTAssert(person.name == name) 308 | XCTAssert(person.age == age) 309 | XCTAssert(person.no == no) 310 | */ 311 | } 312 | 313 | static var allTests = [ 314 | "testGeneric": testGeneric, 315 | "testAny": testAny, 316 | "testJSONString": testJSONString, 317 | "testJSONData": testJSONData, 318 | "testNSNull": testNSNull, 319 | "testLet": testLet, 320 | "testClass": testClass, 321 | "testNSObjectClass": testNSObjectClass, 322 | "testConvert": testConvert, 323 | "testCallback1": testCallback1, 324 | "testCallback2": testCallback2, 325 | "testOCModel": testOCModel 326 | ] 327 | } 328 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/JSON_To_Model/JTM_03_NestedModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTM_03_NestedModel.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/10. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | class JTM_03_NestedModel: XCTestCase { 13 | func testNormal() { 14 | struct Book: Convertible { 15 | var name: String = "" 16 | var price: Double = 0.0 17 | } 18 | 19 | struct Car: Convertible { 20 | var name: String = "" 21 | var price: Double = 0.0 22 | } 23 | 24 | struct Dog: Convertible { 25 | var name: String = "" 26 | var age: Int = 0 27 | } 28 | 29 | struct Person: Convertible { 30 | var name: String = "" 31 | var car: Car? 32 | var books: [Book]? 33 | var dogs: [String: Dog]? 34 | } 35 | 36 | let name = "Jack" 37 | let car = (name: "BMW7", price: 109.5) 38 | let books = [ 39 | (name: "Fast C++", price: 666), 40 | (name: "Data Structure And Algorithm", price: 1666) 41 | ] 42 | let dogs = [ 43 | (name: "Larry", age: 5), 44 | (name: "Wangwang", age: 2) 45 | ] 46 | 47 | let json: [String: Any] = [ 48 | "name": name, 49 | "car": ["name": car.name, "price": car.price], 50 | "books": [ 51 | ["name": books[0].name, "price": books[0].price], 52 | ["name": books[1].name, "price": books[1].price] 53 | ], 54 | "dogs": [ 55 | "dog0": ["name": dogs[0].name, "age": dogs[0].age], 56 | "dog1": ["name": dogs[1].name, "age": dogs[1].age] 57 | ] 58 | ] 59 | 60 | let person = json.kj.model(Person.self) 61 | XCTAssert(person.name == name) 62 | 63 | XCTAssert(person.car?.name == car.name) 64 | XCTAssert(person.car?.price == car.price) 65 | 66 | XCTAssert(person.books?.count == books.count) 67 | XCTAssert(person.books?[0].name == books[0].name) 68 | XCTAssert(person.books?[0].price == Double(books[0].price)) 69 | XCTAssert(person.books?[1].name == books[1].name) 70 | XCTAssert(person.books?[1].price == Double(books[1].price)) 71 | 72 | XCTAssert(person.dogs?.count == dogs.count) 73 | XCTAssert(person.dogs?["dog0"]?.name == dogs[0].name) 74 | XCTAssert(person.dogs?["dog0"]?.age == dogs[0].age) 75 | XCTAssert(person.dogs?["dog1"]?.name == dogs[1].name) 76 | XCTAssert(person.dogs?["dog1"]?.age == dogs[1].age) 77 | } 78 | 79 | func testDefaultValue() { 80 | struct Car: Convertible { 81 | var name: String = "" 82 | var price: Double = 0.0 83 | } 84 | 85 | class Dog: Convertible { 86 | var name: String = "" 87 | var age: Int = 0 88 | required init() {} 89 | init(name: String, age: Int) { 90 | self.name = name 91 | self.age = age 92 | } 93 | } 94 | 95 | struct Person: Convertible { 96 | var name: String = "" 97 | // KakaJSON will use your defaultValue instead of creating a new model 98 | // KakaJSON will not creat a new model again if you already have a default model value 99 | var car: Car = Car(name: "Bently", price: 106.5) 100 | var dog: Dog = Dog(name: "Larry", age: 5) 101 | } 102 | 103 | let json: [String: Any] = [ 104 | "name": "Jake", 105 | "car": ["price": 305.6], 106 | "dog": ["name": "Wangwang"] 107 | ] 108 | 109 | let person = json.kj.model(Person.self) 110 | XCTAssert(person.name == "Jake") 111 | // keep defaultValue 112 | XCTAssert(person.car.name == "Bently") 113 | // use value from json 114 | XCTAssert(person.car.price == 305.6) 115 | // use value from json 116 | XCTAssert(person.dog.name == "Wangwang") 117 | // keep defaultValue 118 | XCTAssert(person.dog.age == 5) 119 | } 120 | 121 | func testRecursive() { 122 | class Person: Convertible { 123 | var name: String = "" 124 | var parent: Person? 125 | required init() {} 126 | } 127 | 128 | let json: [String: Any] = [ 129 | "name": "Jack", 130 | "parent": ["name": "Jim"] 131 | ] 132 | 133 | let person = json.kj.model(Person.self) 134 | XCTAssert(person.name == "Jack") 135 | XCTAssert(person.parent?.name == "Jim") 136 | } 137 | 138 | func testGeneric() { 139 | struct User: Convertible { 140 | let id: String = "" 141 | let nickName: String = "" 142 | } 143 | 144 | struct Goods: Convertible { 145 | private(set) var price: CGFloat = 0.0 146 | let name: String = "" 147 | } 148 | 149 | struct NetResponse: Convertible { 150 | let data: Element? = nil 151 | let msg: String = "" 152 | private(set) var code: Int = 0 153 | } 154 | 155 | let json1 = """ 156 | { 157 | "data": {"nickName": "KaKa", "id": 213234234}, 158 | "msg": "Success", 159 | "code" : 200 160 | } 161 | """ 162 | let response1 = json1.kj.model(NetResponse.self) 163 | XCTAssert(response1?.msg == "Success") 164 | XCTAssert(response1?.code == 200) 165 | XCTAssert(response1?.data?.nickName == "KaKa") 166 | XCTAssert(response1?.data?.id == "213234234") 167 | 168 | let json2 = """ 169 | { 170 | "data": [ 171 | {"price": "6199", "name": "iPhone XR"}, 172 | {"price": "8199", "name": "iPhone XS"}, 173 | {"price": "9099", "name": "iPhone Max"} 174 | ], 175 | "msg": "Success", 176 | "code" : 200 177 | } 178 | """ 179 | let response2 = json2.kj.model(NetResponse<[Goods]>.self) 180 | XCTAssert(response2?.msg == "Success") 181 | XCTAssert(response2?.code == 200) 182 | XCTAssert(response2?.data?.count == 3) 183 | XCTAssert(response2?.data?[0].price == 6199) 184 | XCTAssert(response2?.data?[0].name == "iPhone XR") 185 | XCTAssert(response2?.data?[1].price == 8199) 186 | XCTAssert(response2?.data?[1].name == "iPhone XS") 187 | XCTAssert(response2?.data?[2].price == 9099) 188 | XCTAssert(response2?.data?[2].name == "iPhone Max") 189 | } 190 | 191 | func testOptional() { 192 | struct Book: Convertible { 193 | var name: String = "" 194 | var price: Double = 0.0 195 | } 196 | 197 | struct Dog: Convertible { 198 | var name: String = "" 199 | var age: Int = 0 200 | } 201 | 202 | struct Person: Convertible { 203 | var name: String = "" 204 | var books: [Book??]? 205 | var dogs: [String: Dog???]? 206 | } 207 | 208 | let name = "Jack" 209 | let books = [ 210 | (name: "Fast C++", price: 666), 211 | (name: "Data Structure And Algorithm", price: 1666) 212 | ] 213 | let dogs = [ 214 | (name: "Larry", age: 5), 215 | (name: "Wangwang", age: 2) 216 | ] 217 | 218 | let json: [String: Any] = [ 219 | "name": name, 220 | "books": [ 221 | ["name": books[0].name, "price": books[0].price], 222 | ["name": books[1].name, "price": books[1].price] 223 | ], 224 | "dogs": [ 225 | "dog0": ["name": dogs[0].name, "age": dogs[0].age], 226 | "dog1": ["name": dogs[1].name, "age": dogs[1].age] 227 | ] 228 | ] 229 | 230 | let person = json.kj.model(Person.self) 231 | XCTAssert(person.name == name) 232 | 233 | XCTAssert(person.books?.count == books.count) 234 | XCTAssert(person.books?[0]??.name == books[0].name) 235 | XCTAssert(person.books?[0]??.price == Double(books[0].price)) 236 | XCTAssert(person.books?[1]??.name == books[1].name) 237 | XCTAssert(person.books?[1]??.price == Double(books[1].price)) 238 | 239 | XCTAssert(person.dogs?.count == dogs.count) 240 | XCTAssert(person.dogs?["dog0"]????.name == dogs[0].name) 241 | XCTAssert(person.dogs?["dog0"]????.age == dogs[0].age) 242 | XCTAssert(person.dogs?["dog1"]????.name == dogs[1].name) 243 | XCTAssert(person.dogs?["dog1"]????.age == dogs[1].age) 244 | } 245 | 246 | func testSet() { 247 | struct Book: Convertible, Hashable { 248 | var name: String = "" 249 | var price: Double = 0.0 250 | } 251 | 252 | struct Person: Convertible { 253 | var name: String = "" 254 | var books: Set? 255 | } 256 | 257 | let name = "Jack" 258 | let bookName = "Fast C++" 259 | let bookPrice = 666.6 260 | 261 | let json: [String: Any] = [ 262 | "name": name, 263 | "books": [ 264 | ["name": bookName, "price": bookPrice] 265 | ] 266 | ] 267 | 268 | let person = json.kj.model(Person.self) 269 | XCTAssert(person.name == name) 270 | 271 | XCTAssert(person.books?.count == 1) 272 | let book = person.books?.randomElement() 273 | XCTAssert(book?.name == bookName) 274 | XCTAssert(book?.price == bookPrice) 275 | } 276 | 277 | func testModelArrayInDictionary() { 278 | struct Book: Convertible { 279 | var name: String = "" 280 | var price: Double = 0.0 281 | } 282 | 283 | struct Person: Convertible { 284 | var name: String = "" 285 | var books: [String: [Book?]?]? 286 | } 287 | 288 | let name = "Jack" 289 | let mobileBooks = [ 290 | (name: "iOS", price: 10.5), 291 | (name: "Android", price: 8.5) 292 | ] 293 | let serverBooks = [ 294 | (name: "Java", price: 20.5), 295 | (name: "Go", price: 18.5) 296 | ] 297 | 298 | let json: [String: Any] = [ 299 | "name": name, 300 | "books": [ 301 | "mobile": [ 302 | ["name": mobileBooks[0].name, "price": mobileBooks[0].price], 303 | ["name": mobileBooks[1].name, "price": mobileBooks[1].price] 304 | ], 305 | "server": [ 306 | ["name": serverBooks[0].name, "price": serverBooks[0].price], 307 | ["name": serverBooks[1].name, "price": serverBooks[1].price] 308 | ] 309 | ] 310 | ] 311 | 312 | let person = json.kj.model(Person.self) 313 | XCTAssert(person.name == name) 314 | let books0 = person.books?["mobile"] 315 | XCTAssert(books0??.count == mobileBooks.count) 316 | for i in 0.. = [ 60 | ["name": tuples[0].name, "price": tuples[0].price] 61 | ] 62 | 63 | let cars = json.kj.modelArray(Car.self) 64 | XCTAssert(cars[0].name == tuples[0].name) 65 | XCTAssert(cars[0].price == tuples[0].price) 66 | } 67 | 68 | func testJSONString() { 69 | let JSONString = """ 70 | [ 71 | {"name":"\(tuples[0].name)","price":\(tuples[0].price)}, 72 | {"name":"\(tuples[1].name)","price":\(tuples[1].price)}, 73 | {"name":"\(tuples[2].name)","price":\(tuples[2].price)} 74 | ] 75 | """ 76 | 77 | let cars = JSONString.kj.modelArray(Car.self) 78 | check(cars) 79 | } 80 | 81 | func check(_ cars: [Car]) { 82 | XCTAssert(cars.count == tuples.count) 83 | XCTAssert(cars[0].name == tuples[0].name) 84 | XCTAssert(cars[0].price == tuples[0].price) 85 | XCTAssert(cars[1].name == tuples[1].name) 86 | XCTAssert(cars[1].price == tuples[1].price) 87 | XCTAssert(cars[2].name == tuples[2].name) 88 | XCTAssert(cars[2].price == tuples[2].price) 89 | } 90 | 91 | static var allTests = [ 92 | "testArray": testArray, 93 | "testNSArray": testNSArray, 94 | "testNSMutableArray": testNSMutableArray, 95 | "testSet": testSet, 96 | "testJSONString": testJSONString 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/JSON_To_Model/JTM_06_CustomValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTM_06_CustomValue.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/10. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | private let date1Fmt: DateFormatter = { 13 | let fmt = DateFormatter() 14 | fmt.dateFormat = "yyyy-MM-dd" 15 | return fmt 16 | }() 17 | 18 | private let date2Fmt: DateFormatter = { 19 | let fmt = DateFormatter() 20 | fmt.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" 21 | return fmt 22 | }() 23 | 24 | class JTM_06_CustomValue: XCTestCase { 25 | // MARK: - Date 26 | func testDate() { 27 | struct Student: Convertible { 28 | var date1: Date? 29 | var date2: NSDate? 30 | 31 | func kj_modelValue(from jsonValue: Any?, 32 | _ property: Property) -> Any? { 33 | switch property.name { 34 | case "date1": return (jsonValue as? String).flatMap(date1Fmt.date) 35 | 36 | // Date to NSDate is a bridging conversion 37 | case "date2": return (jsonValue as? String).flatMap(date2Fmt.date) 38 | default: return jsonValue 39 | } 40 | } 41 | } 42 | 43 | let date1 = "2008-09-09" 44 | let date2 = "2011-11-12 14:20:30.888" 45 | 46 | let json: [String: Any] = [ 47 | "date1": date1, 48 | "date2": date2 49 | ] 50 | 51 | let student = json.kj.model(Student.self) 52 | XCTAssert(student.date1.flatMap(date1Fmt.string) == date1) 53 | XCTAssert(student.date2.flatMap(date2Fmt.string) == date2) 54 | } 55 | 56 | // MARK: - Any 57 | func testAny() { 58 | class Dog: Convertible { 59 | var name: String = "" 60 | var weight: Double = 0.0 61 | required init() {} 62 | } 63 | 64 | struct Person: Convertible { 65 | var name: String = "" 66 | // AnyObject、Convertible 67 | var pet: Any? 68 | func kj_modelValue(from jsonValue: Any?, 69 | _ property: Property) -> Any? { 70 | if property.name != "pet" { return jsonValue } 71 | return (jsonValue as? [String: Any])?.kj.model(Dog.self) 72 | } 73 | } 74 | 75 | let name = "Jack" 76 | let dog = (name: "Wang", weight: 109.5) 77 | 78 | let json: [String: Any] = [ 79 | "name": name, 80 | "pet": ["name": dog.name, "weight": dog.weight] 81 | ] 82 | 83 | let person = json.kj.model(Person.self) 84 | XCTAssert(person.name == name) 85 | 86 | let pet = person.pet as? Dog 87 | XCTAssert(pet?.name == dog.name) 88 | XCTAssert(pet?.weight == dog.weight) 89 | } 90 | 91 | // MARK: - Array 92 | func testAnyArray() { 93 | class Book: Convertible { 94 | var name: String = "" 95 | var price: Double = 0.0 96 | required init() {} 97 | } 98 | 99 | struct Person: Convertible { 100 | var name: String = "" 101 | // [AnyObject]、[Convertible]、NSArray、NSMutableArray 102 | var books: [Any]? 103 | 104 | func kj_modelValue(from jsonValue: Any?, 105 | _ property: Property) -> Any? { 106 | if property.name != "books" { return jsonValue } 107 | // if books is `NSMutableArray`, neet convert `Array` to `NSMutableArray` 108 | // because `Array` to `NSMutableArray` is not a bridging conversion 109 | return (jsonValue as? [Any])?.kj.modelArray(Book.self) 110 | } 111 | } 112 | 113 | let name = "Jack" 114 | let books = [ 115 | (name: "Fast C++", price: 666), 116 | (name: "Data Structure And Algorithm", price: 1666) 117 | ] 118 | 119 | let json: [String: Any] = [ 120 | "name": name, 121 | "books": [ 122 | ["name": books[0].name, "price": books[0].price], 123 | ["name": books[1].name, "price": books[1].price] 124 | ] 125 | ] 126 | 127 | let person = json.kj.model(Person.self) 128 | XCTAssert(person.name == name) 129 | 130 | XCTAssert(person.books?.count == books.count) 131 | 132 | let book0 = person.books?[0] as? Book 133 | XCTAssert(book0?.name == books[0].name) 134 | XCTAssert(book0?.price == Double(books[0].price)) 135 | 136 | let book1 = person.books?[1] as? Book 137 | XCTAssert(book1?.name == books[1].name) 138 | XCTAssert(book1?.price == Double(books[1].price)) 139 | } 140 | 141 | // MARK: - NestedArray 142 | func testNestedArray() { 143 | struct Dog: Convertible { 144 | var name: String = "" 145 | var weight: Double = 0.0 146 | } 147 | 148 | struct Person: Convertible { 149 | var name: String = "" 150 | var pet: [[Dog]]? 151 | func kj_modelValue(from jsonValue: Any?, 152 | _ property: Property) -> Any? { 153 | if property.name != "pet" { return jsonValue } 154 | return (jsonValue as? [[[String: Any]]])?.map { 155 | $0.kj.modelArray(Dog.self) 156 | } 157 | } 158 | } 159 | 160 | let name = "Jack" 161 | let dog = (name: "Wang", weight: 109.5) 162 | 163 | let json: [String: Any] = [ 164 | "name": name, 165 | "pet": [ 166 | [["name": dog.name, "weight": dog.weight]], 167 | [["name": dog.name, "weight": dog.weight]] 168 | ] 169 | ] 170 | 171 | let person = json.kj.model(Person.self) 172 | XCTAssert(person.name == name) 173 | 174 | XCTAssert(person.pet?[0][0].name == dog.name) 175 | XCTAssert(person.pet?[0][0].weight == dog.weight) 176 | XCTAssert(person.pet?[1][0].name == dog.name) 177 | XCTAssert(person.pet?[1][0].weight == dog.weight) 178 | } 179 | 180 | // MARK: - Other 181 | func testOther1() { 182 | struct Student: Convertible { 183 | var age: Int = 0 184 | var name: String = "" 185 | 186 | func kj_modelValue(from jsonValue: Any?, 187 | _ property: Property) -> Any? { 188 | switch property.name { 189 | case "age": return (jsonValue as? Int).flatMap { $0 + 5 } 190 | case "name": return (jsonValue as? String).flatMap { "kj_" + $0 } 191 | default: return jsonValue 192 | } 193 | } 194 | } 195 | 196 | let json: [String: Any] = [ 197 | "age": 10, 198 | "name": "Jack" 199 | ] 200 | 201 | let student = json.kj.model(Student.self) 202 | XCTAssert(student.age == 15) 203 | XCTAssert(student.name == "kj_Jack") 204 | } 205 | 206 | func testOther2() { 207 | struct Student: Convertible { 208 | var age: Int = 0 209 | var name: String = "" 210 | 211 | mutating func kj_didConvertToModel(from json: [String: Any]) { 212 | age += 5 213 | name = "kj_" + name 214 | } 215 | } 216 | 217 | let json: [String: Any] = [ 218 | "age": 10, 219 | "name": "Jack" 220 | ] 221 | 222 | let student = json.kj.model(Student.self) 223 | XCTAssert(student.age == 15) 224 | XCTAssert(student.name == "kj_Jack") 225 | } 226 | 227 | static var allTests = [ 228 | "testDate": testDate, 229 | "testAny": testAny, 230 | "testAnyArray": testAnyArray, 231 | "testNestedArray": testNestedArray, 232 | "testOther1": testOther1, 233 | "testOther2": testOther2 234 | ] 235 | } 236 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/JSON_To_Model/JTM_07_DynamicModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTM_07_DynamicModel.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/12. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | class JTM_07_DynamicModel: XCTestCase { 13 | class Book: Convertible { 14 | var name: String = "" 15 | var price: Double = 0.0 16 | required init() {} 17 | } 18 | 19 | class Car: Convertible { 20 | var name: String = "" 21 | var price: Double = 0.0 22 | required init() {} 23 | } 24 | 25 | class Pig: Convertible { 26 | var name: String = "" 27 | var height: Double = 0.0 28 | required init() {} 29 | } 30 | 31 | class Dog: Convertible { 32 | var name: String = "" 33 | var weight: Double = 0.0 34 | required init() {} 35 | } 36 | 37 | struct Person: Convertible { 38 | var name: String = "" 39 | // AnyObject、Convertible 40 | var pet: Any? 41 | // [AnyObject]、[Convertible]、NSArray、NSMutableArray 42 | var toys: [Any]? 43 | // [String: AnyObject]、[String: Convertible] 44 | // NSDictionary、NSMutableDictionary 45 | var foods: [String: Any]? 46 | 47 | func kj_modelType(from jsonValue: Any?, 48 | _ property: Property) -> Convertible.Type? { 49 | switch property.name { 50 | case "pet": 51 | if let pet = jsonValue as? [String: Any], 52 | let _ = pet["height"] { 53 | return Pig.self 54 | } 55 | return Dog.self 56 | case "toys": return Car.self 57 | case "foods": return Book.self 58 | default: return nil 59 | } 60 | } 61 | } 62 | 63 | func test() { 64 | let name = "Jack" 65 | let dog = (name: "Wang", weight: 109.5) 66 | let pig = (name: "Keke", height: 1.55) 67 | let books = [ 68 | (name: "Fast C++", price: 666.0), 69 | (name: "Data Structure And Algorithm", price: 1666.0) 70 | ] 71 | let cars = [ 72 | (name: "Benz", price: 100.5), 73 | (name: "Bently", price: 300.6) 74 | ] 75 | 76 | let json: [String: Any] = [ 77 | "name": name, 78 | "pet": ["name": dog.name, "weight": dog.weight], 79 | // "pet": ["name": pig.name, "height": pig.height], 80 | "toys": [ 81 | ["name": cars[0].name, "price": cars[0].price], 82 | ["name": cars[1].name, "price": cars[1].price] 83 | ], 84 | "foods": [ 85 | "food0": ["name": books[0].name, "price": books[0].price], 86 | "food1": ["name": books[1].name, "price": books[1].price] 87 | ] 88 | ] 89 | 90 | let person = json.kj.model(Person.self) 91 | XCTAssert(person.name == name) 92 | 93 | if let pet = person.pet as? Dog { 94 | XCTAssert(pet.name == dog.name) 95 | XCTAssert(pet.weight == dog.weight) 96 | } else if let pet = person.pet as? Pig { 97 | XCTAssert(pet.name == pig.name) 98 | XCTAssert(pet.height == pig.height) 99 | } 100 | 101 | let toy0 = person.toys?[0] as? Car 102 | XCTAssert(toy0?.name == cars[0].name) 103 | XCTAssert(toy0?.price == cars[0].price) 104 | 105 | let toy1 = person.toys?[1] as? Car 106 | XCTAssert(toy1?.name == cars[1].name) 107 | XCTAssert(toy1?.price == cars[1].price) 108 | 109 | let food0 = person.foods?["food0"] as? Book 110 | XCTAssert(food0?.name == books[0].name) 111 | XCTAssert(food0?.price == books[0].price) 112 | 113 | let food1 = person.foods?["food1"] as? Book 114 | XCTAssert(food1?.name == books[1].name) 115 | XCTAssert(food1?.price == books[1].price) 116 | } 117 | 118 | static var allTests = [ 119 | "test": test 120 | ] 121 | } 122 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/Model_To_JSON/MTJ_01_Basic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTJ_01_Basic.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/11. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | class MTJ_01_Basic: XCTestCase { 13 | // MARK: - Normal 14 | func testNormal() { 15 | // Equatable is only for test cases, is not necessary for Model-To-JSON. 16 | struct Car: Convertible, Equatable { 17 | var name: String = "Bently" 18 | var new: Bool = true 19 | var age: Int = 10 20 | var area: Float = longFloat 21 | var weight: Double = longDouble 22 | var height: Decimal = longDecimal 23 | var price: NSDecimalNumber = longDecimalNumber 24 | var minSpeed: Double = 66.66 25 | var maxSpeed: NSNumber = 77.77 26 | var capacity: CGFloat = 88.88 27 | var birthday: Date = time 28 | var url: URL? = URL(string: "http://520suanfa.com") 29 | } 30 | 31 | let car = Car() 32 | 33 | let json = car.kj.JSONObject() 34 | // let json = JSONObject(from: car) 35 | XCTAssert(Values.bool(json["new"]) == true) 36 | XCTAssert(Values.int(json["age"]) == 10) 37 | XCTAssert(Values.float(json["area"]) == longFloat) 38 | XCTAssert(Values.double(json["weight"]) == longDouble) 39 | XCTAssert(Values.decimal(json["height"]) == longDecimal) 40 | XCTAssert(Values.string(json["name"]) == "Bently") 41 | XCTAssert(Values.decimalNumber(json["price"]) == longDecimalNumber) 42 | XCTAssert(Values.double(json["minSpeed"]) == 66.66) 43 | XCTAssert(Values.number(json["maxSpeed"]) == 77.77) 44 | XCTAssert(Values.cgFloat(json["capacity"]) == 88.88) 45 | XCTAssert(Values.double(json["birthday"]) == timeInteval) 46 | XCTAssert(Values.string(json["url"]) == "http://520suanfa.com") 47 | 48 | var jsonString = car.kj.JSONString() 49 | // var jsonString = JSONString(from: car) 50 | /* {"birthday":1565922866,"new":true,"height":0.123456789012345678901234567890123456789,"weight":0.1234567890123456,"minSpeed":66.66,"price":0.123456789012345678901234567890123456789,"age":10,"name":"Bently","area":0.12345678,"maxSpeed":77.77,"capacity":88.88,"url":"http:\/\/520suanfa.com"} */ 51 | 52 | XCTAssert(jsonString.contains("Bently") == true) 53 | XCTAssert(jsonString.contains("true") == true) 54 | XCTAssert(jsonString.contains("10") == true) 55 | XCTAssert(jsonString.contains(longFloatString) == true) 56 | XCTAssert(jsonString.contains(longDoubleString) == true) 57 | XCTAssert(jsonString.contains(longDecimalString) == true) 58 | XCTAssert(jsonString.contains("66.66") == true) 59 | XCTAssert(jsonString.contains("77.77") == true) 60 | XCTAssert(jsonString.contains("88.88") == true) 61 | XCTAssert(jsonString.contains(timeIntevalString) == true) 62 | XCTAssert(jsonString.contains("520suanfa.com") == true) 63 | 64 | jsonString = car.kj.JSONString(prettyPrinted: true) 65 | /* 66 | { 67 | "height" : 0.123456789012345678901234567890123456789, 68 | "weight" : 0.1234567890123456, 69 | "minSpeed" : 66.66, 70 | "new" : true, 71 | "maxSpeed" : 77.77, 72 | "age" : 10, 73 | "capacity" : 88.88, 74 | "birthday" : 1565922866, 75 | "name" : "Bently", 76 | "price" : 0.123456789012345678901234567890123456789, 77 | "area" : 0.12345678 78 | } 79 | */ 80 | 81 | // try? jsonString.write(toFile: "/Users/mj/Desktop/car.json", atomically: true, encoding: .utf8) 82 | 83 | checkModelToJSon(Car.self) 84 | } 85 | 86 | // MARK: - Optional Type 87 | func testOptional() { 88 | // Equatable is only for test cases, is not necessary for Model-To-JSON. 89 | struct Student: Convertible, Equatable { 90 | var op1: Int? = 10 91 | var op2: Double?? = 66.66 92 | var op3: Float??? = 77.77 93 | var op4: String???? = "Jack" 94 | var op5: Bool????? = true 95 | // NSArray\Set\NSSet 96 | // NSMutableArray\NSMutableSet 97 | var op6: [CGFloat]?????? = [44.44, 55.55] 98 | } 99 | 100 | let jsonString = Student().kj.JSONString() 101 | /* 102 | { 103 | "op1" : 10, 104 | "op4" : "Jack", 105 | "op2" : 66.66, 106 | "op5" : true, 107 | "op6" : [ 108 | 44.44, 109 | 55.55 110 | ], 111 | "op3" : 77.77 112 | } 113 | */ 114 | 115 | XCTAssert(jsonString.contains("66.66") == true) 116 | XCTAssert(jsonString.contains("77.77") == true) 117 | XCTAssert(jsonString.contains("true") == true) 118 | XCTAssert(jsonString.contains("44.44") == true) 119 | XCTAssert(jsonString.contains("55.55") == true) 120 | 121 | checkModelToJSon(Student.self) 122 | } 123 | 124 | // MARK: - Enum Type 125 | func testEnum1() { 126 | enum Grade: String, ConvertibleEnum { 127 | case perfect = "A" 128 | case great = "B" 129 | case good = "C" 130 | case bad = "D" 131 | } 132 | 133 | // Equatable is only for test cases, is not necessary for Model-To-JSON. 134 | struct Student: Convertible, Equatable { 135 | var grade1: Grade = .great 136 | var grade2: Grade = .bad 137 | } 138 | 139 | let jsonString = Student().kj.JSONString() 140 | /* {"grade2":"D","grade1":"B"} */ 141 | 142 | XCTAssert(jsonString.contains("B") == true) 143 | XCTAssert(jsonString.contains("D") == true) 144 | 145 | checkModelToJSon(Student.self) 146 | } 147 | 148 | func testEnum2() { 149 | enum Grade: Double, ConvertibleEnum { 150 | case perfect = 8.88 151 | case great = 7.77 152 | case good = 6.66 153 | case bad = 5.55 154 | } 155 | 156 | struct Student: Convertible, Equatable { 157 | var grade1: Grade = .perfect 158 | var grade2: Grade = .great 159 | var grade3: Grade = .good 160 | var grade4: Grade = .bad 161 | } 162 | 163 | let jsonString = Student().kj.JSONString() 164 | XCTAssert(jsonString.contains("5.55") == true) 165 | XCTAssert(jsonString.contains("6.66") == true) 166 | XCTAssert(jsonString.contains("7.77") == true) 167 | XCTAssert(jsonString.contains("8.88") == true) 168 | 169 | checkModelToJSon(Student.self) 170 | } 171 | 172 | static var allTests = [ 173 | "testNormal": testNormal, 174 | "testOptional": testOptional, 175 | "testEnum1": testEnum1, 176 | "testEnum2": testEnum2 177 | ] 178 | } 179 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/Model_To_JSON/MTJ_02_NestedModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTJ_02_NestedModel.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/12. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | class MTJ_02_NestedModel: XCTestCase { 13 | func test() { 14 | // Equatable is only for test cases, is not necessary for Model-To-JSON. 15 | struct Book: Convertible, Equatable { 16 | var name: String = "" 17 | var price: Double = 0.0 18 | } 19 | 20 | // Equatable is only for test cases, is not necessary for Model-To-JSON. 21 | struct Car: Convertible, Equatable { 22 | var name: String = "" 23 | var price: Double = 0.0 24 | } 25 | 26 | // Equatable is only for test cases, is not necessary for Model-To-JSON. 27 | struct Dog: Convertible, Equatable { 28 | var name: String = "" 29 | var age: Int = 0 30 | } 31 | 32 | // Equatable is only for test cases, is not necessary for Model-To-JSON. 33 | struct Person: Convertible, Equatable { 34 | var name: String = "Jack" 35 | var car: Car? = Car(name: "Bently", price: 106.666) 36 | var books: [Book]? = [ 37 | Book(name: "Fast C++", price: 666.6), 38 | Book(name: "Data Structure And Algorithm", price: 666.6), 39 | ] 40 | var dogs: [String: Dog]? = [ 41 | "dog0": Dog(name: "Wang", age: 5), 42 | "dog1": Dog(name: "ErHa", age: 3), 43 | ] 44 | } 45 | 46 | let jsonString = Person().kj.JSONString() 47 | /* 48 | { 49 | "dogs" : { 50 | "dog0" : { 51 | "name" : "Wang", 52 | "age" : 5 53 | }, 54 | "dog1" : { 55 | "name" : "ErHa", 56 | "age" : 3 57 | } 58 | }, 59 | "books" : [ 60 | { 61 | "price" : 666.6, 62 | "name" : "Fast C++" 63 | }, 64 | { 65 | "name" : "Data Structure And Algorithm", 66 | "price" : 666.6 67 | } 68 | ], 69 | "name" : "Jack", 70 | "car" : { 71 | "price" : 106.666, 72 | "name" : "Bently" 73 | } 74 | } 75 | */ 76 | 77 | XCTAssert(jsonString.contains("106.666") == true) 78 | XCTAssert(jsonString.contains("666.6") == true) 79 | XCTAssert(jsonString.contains("Data Structure And Algorithm") == true) 80 | 81 | checkModelToJSon(Person.self) 82 | } 83 | 84 | func testAny() { 85 | struct Book: Convertible { 86 | var name: String = "" 87 | var price: Double = 0.0 88 | } 89 | 90 | struct Dog: Convertible { 91 | var name: String = "" 92 | var age: Int = 0 93 | } 94 | 95 | struct Person: Convertible { 96 | // NSArray\NSMutableArray 97 | var books: [Any]? = [ 98 | Book(name: "Fast C++", price: 666.6), 99 | Book(name: "Data Structure And Algorithm", price: 1666.6), 100 | ] 101 | 102 | // NSDictionary\NSMutableDictionary 103 | var dogs: [String: Any]? = [ 104 | "dog0": Dog(name: "Wang", age: 5), 105 | "dog1": Dog(name: "ErHa", age: 3), 106 | ] 107 | } 108 | 109 | let jsonString = Person().kj.JSONString() 110 | /* 111 | { 112 | "dogs" : { 113 | "dog1" : { 114 | "age" : 3, 115 | "name" : "ErHa" 116 | }, 117 | "dog0" : { 118 | "age" : 5, 119 | "name" : "Wang" 120 | } 121 | }, 122 | "books" : [ 123 | { 124 | "name" : "Fast C++", 125 | "price" : 666.6 126 | }, 127 | { 128 | "price" : 1666.6, 129 | "name" : "Data Structure And Algorithm" 130 | } 131 | ] 132 | } 133 | */ 134 | XCTAssert(jsonString.contains("1666.6") == true) 135 | XCTAssert(jsonString.contains("666.6") == true) 136 | XCTAssert(jsonString.contains("Fast C++") == true) 137 | } 138 | 139 | static var allTests = [ 140 | "test": test, 141 | "testAny": testAny 142 | ] 143 | } 144 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/Model_To_JSON/MTJ_03_ModelArray.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTJ_03_ModelArray.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/12. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | class MTJ_03_ModelArray: XCTestCase { 13 | 14 | func testArray() { 15 | struct Car: Convertible { 16 | var name: String = "" 17 | var price: Double = 0.0 18 | } 19 | 20 | // NSArray\NSMutableArray 21 | let models = [ 22 | Car(name: "BMW", price: 100.0), 23 | Car(name: "Audi", price: 70.0), 24 | Car(name: "Bently", price: 300.0) 25 | ] 26 | 27 | let jsonString = models.kj.JSONString() 28 | /* 29 | [ 30 | { 31 | "name" : "BMW", 32 | "price" : 100 33 | }, 34 | { 35 | "price" : 70, 36 | "name" : "Audi" 37 | }, 38 | { 39 | "price" : 300, 40 | "name" : "Bently" 41 | } 42 | ] 43 | */ 44 | XCTAssert(jsonString.contains("BMW") == true) 45 | XCTAssert(jsonString.contains("100") == true) 46 | XCTAssert(jsonString.contains("Audi") == true) 47 | XCTAssert(jsonString.contains("70") == true) 48 | XCTAssert(jsonString.contains("Bently") == true) 49 | XCTAssert(jsonString.contains("300") == true) 50 | } 51 | 52 | func testSet() { 53 | struct Car: Convertible, Hashable { 54 | var name: String = "" 55 | var price: Double = 0.0 56 | } 57 | 58 | let models: Set = [ 59 | Car(name: "BMW", price: 100.0), 60 | Car(name: "Audi", price: 70.0), 61 | Car(name: "Bently", price: 300.0) 62 | ] 63 | 64 | let jsonString = models.kj.JSONString() 65 | /* 66 | [ 67 | { 68 | "price" : 70, 69 | "name" : "Audi" 70 | }, 71 | { 72 | "price" : 300, 73 | "name" : "Bently" 74 | }, 75 | { 76 | "name" : "BMW", 77 | "price" : 100 78 | } 79 | ] 80 | */ 81 | XCTAssert(jsonString.contains("BMW") == true) 82 | XCTAssert(jsonString.contains("100") == true) 83 | XCTAssert(jsonString.contains("Audi") == true) 84 | XCTAssert(jsonString.contains("70") == true) 85 | XCTAssert(jsonString.contains("Bently") == true) 86 | XCTAssert(jsonString.contains("300") == true) 87 | } 88 | 89 | static var allTests = [ 90 | "testArray": testArray, 91 | "testSet": testSet 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/Model_To_JSON/MTJ_04_KeyMapping.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTJ_04_KeyMapping.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/13. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | class MTJ_04_KeyMapping: XCTestCase { 13 | struct Dog: Convertible { 14 | var nickName: String = "Wang" 15 | var price: Double = 100.6 16 | 17 | func kj_JSONKey(from property: Property) -> JSONPropertyKey { 18 | switch property.name { 19 | case "nickName": return "_nick_name_" 20 | default: return property.name 21 | } 22 | } 23 | } 24 | 25 | func test() { 26 | let jsonString = Dog().kj.JSONString() 27 | /* {"price":100.6,"_nick_name_":"Wang"} */ 28 | XCTAssert(jsonString.contains("_nick_name_") == true) 29 | } 30 | 31 | static var allTests = [ 32 | "test": test 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/Model_To_JSON/MTJ_05_CustomValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MTJ_05_CustomValue.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/13. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | private let dateFmt: DateFormatter = { 13 | let fmt = DateFormatter() 14 | fmt.dateFormat = "yyyy-MM-dd HH:mm:ss" 15 | return fmt 16 | }() 17 | 18 | class MTJ_05_CustomValue: XCTestCase { 19 | // MARK: - Date 20 | func testDate() { 21 | struct Student: Convertible { 22 | var birthday: Date? 23 | 24 | func kj_JSONValue(from modelValue: Any?, 25 | _ property: Property) -> Any? { 26 | if property.name != "birthday" { return modelValue } 27 | return birthday.flatMap(dateFmt.string) 28 | } 29 | } 30 | 31 | let time = "2019-08-13 12:52:51" 32 | let date = dateFmt.date(from: time) 33 | let student = Student(birthday: date) 34 | let jsonString = student.kj.JSONString() 35 | /* {"birthday":"2019-08-13 12:52:51"} */ 36 | XCTAssert(jsonString.contains(time) == true) 37 | } 38 | 39 | static var allTests = [ 40 | "testDate": testDate 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/OC/KakaJSONTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "OCModel.h" 6 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/OC/OCModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // OCModel.h 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/12/23. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface OCModel : NSObject 13 | @property (nonatomic, assign) int no; 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/OC/OCModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // OCModel.m 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/12/23. 6 | // 7 | 8 | #import "OCModel.h" 9 | 10 | @implementation OCModel 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/Other/Bugs.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bugs.swift 3 | // KakaJSON 4 | // 5 | // Created by MJ Lee on 2020/1/7. 6 | // 7 | 8 | import XCTest 9 | @testable import KakaJSON 10 | 11 | class TestBugs: XCTestCase { 12 | // https://github.com/kakaopensource/KakaJSON/issues/39 13 | func testIssue39() { 14 | struct ChatRoomModel: Convertible { 15 | var lastMessage: String = "" 16 | var lastMessageTime: NSNumber = 0 17 | var pkid: NSNumber = 0 18 | var unreadNoticeCount: NSNumber = 0 19 | } 20 | struct WorkShopsModel: Convertible { 21 | var chatRoom: ChatRoomModel? 22 | var imgUrl: String = "" 23 | var isMine: NSNumber = false 24 | var pkid: NSNumber = 0 25 | var unsignined: NSNumber? 26 | var status: NSNumber = 0 27 | var title: String = "" 28 | var regCount: Int? 29 | var tbString: String? 30 | var teString: String? 31 | } 32 | 33 | let json: [String: Any] = [ 34 | "chatRoom": [ 35 | "last_message": "666", 36 | "last_message_time": 1567578912000, 37 | "pkid": 2, 38 | "unread_notice_count": 10 39 | ], 40 | "img_url": "xx.jpg", 41 | "is_mine": true, 42 | "pkid": 3, 43 | "reg_count": 1, 44 | "status": 1, 45 | "tbString": "2019-01-30", 46 | "teString": "2019-09-28", 47 | "title": "777", 48 | "unsignined": true 49 | ] 50 | 51 | ConvertibleConfig.setModelKey { (property) in 52 | return property.name.kj.underlineCased() 53 | // return [property.name.kj.underlineCased()] 54 | } 55 | 56 | let workShops = json.kj.model(WorkShopsModel.self) 57 | XCTAssert(workShops.chatRoom?.lastMessage == "666") 58 | XCTAssert(workShops.chatRoom?.pkid == 2) 59 | XCTAssert(workShops.chatRoom?.unreadNoticeCount == 10) 60 | XCTAssert(workShops.chatRoom?.lastMessageTime == 1567578912000) 61 | XCTAssert(workShops.imgUrl == "xx.jpg") 62 | XCTAssert(workShops.isMine == true) 63 | XCTAssert(workShops.pkid == 3) 64 | XCTAssert(workShops.regCount == 1) 65 | XCTAssert(workShops.status == 1) 66 | XCTAssert(workShops.tbString == "2019-01-30") 67 | XCTAssert(workShops.teString == "2019-09-28") 68 | XCTAssert(workShops.title == "777") 69 | XCTAssert(workShops.unsignined == true) 70 | } 71 | 72 | static var allTests = [ 73 | "testIssue39": testIssue39 74 | ] 75 | } 76 | 77 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/Other/Coding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coding.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/22. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | class TestCoding: XCTestCase { 13 | // Please input your file path 14 | var file: String = { 15 | var str = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0] 16 | str.append("/kj_test.json") 17 | return str 18 | }() 19 | 20 | func testModel() { 21 | // Equatable is only for test cases, is not necessary for Coding. 22 | struct Car: Convertible, Equatable { 23 | var name: String = "Bently" 24 | var new: Bool = true 25 | var age: Int = 10 26 | var area: Float = longFloat 27 | var weight: Double = longDouble 28 | var height: Decimal = longDecimal 29 | var price: NSDecimalNumber = longDecimalNumber 30 | var minSpeed: Double = 66.66 31 | var maxSpeed: NSNumber = 77.77 32 | var capacity: CGFloat = 88.88 33 | var birthday: Date = time 34 | var url: URL? = URL(string: "http://520suanfa.com") 35 | } 36 | 37 | checkCoding(Car.self) 38 | } 39 | 40 | func testOptional() { 41 | // Equatable is only for test cases, is not necessary for Coding. 42 | struct Student: Convertible, Equatable { 43 | var op1: Int? = 10 44 | var op2: Double?? = 66.66 45 | var op3: Float??? = 77.77 46 | var op4: String???? = "Jack" 47 | var op5: Bool????? = true 48 | // NSArray\Set\NSSet 49 | // NSMutableArray\NSMutableSet 50 | var op6: [CGFloat]?????? = [44.44, 55.55] 51 | } 52 | 53 | checkCoding(Student.self) 54 | } 55 | 56 | func testNested() { 57 | // Equatable is only for test cases, is not necessary for Coding. 58 | struct Book: Convertible, Equatable { 59 | var name: String = "" 60 | var price: Double = 0.0 61 | } 62 | 63 | // Equatable is only for test cases, is not necessary for Coding. 64 | struct Car: Convertible, Equatable { 65 | var name: String = "" 66 | var price: Double = 0.0 67 | } 68 | 69 | // Equatable is only for test cases, is not necessary for Coding. 70 | struct Dog: Convertible, Equatable { 71 | var name: String = "" 72 | var age: Int = 0 73 | } 74 | 75 | // Equatable is only for test cases, is not necessary for Coding. 76 | struct Person: Convertible, Equatable { 77 | var name: String = "Jack" 78 | var car: Car? = Car(name: "Bently", price: 106.666) 79 | var books: [Book]? = [ 80 | Book(name: "Fast C++", price: 666.6), 81 | Book(name: "Data Structure And Algorithm", price: 666.6), 82 | ] 83 | var dogs: [String: Dog]? = [ 84 | "dog0": Dog(name: "Wang", age: 5), 85 | "dog1": Dog(name: "ErHa", age: 3), 86 | ] 87 | } 88 | 89 | checkCoding(Person.self) 90 | 91 | write(Person(), to: file) 92 | let person = read(Person.self, from: file) 93 | 94 | XCTAssert(person?.name == "Jack") 95 | XCTAssert(person?.car?.name == "Bently") 96 | XCTAssert(person?.car?.price == 106.666) 97 | XCTAssert(person?.books?.count == 2) 98 | XCTAssert(person?.dogs?.count == 2) 99 | } 100 | 101 | func testAny() { 102 | struct Book: Convertible { 103 | var name: String = "" 104 | var price: Double = 0.0 105 | } 106 | 107 | struct Dog: Convertible { 108 | var name: String = "" 109 | var age: Int = 0 110 | } 111 | 112 | struct Person: Convertible { 113 | // NSArray\NSMutableArray 114 | var books: [Any]? = [ 115 | Book(name: "Fast C++", price: 666.6), 116 | Book(name: "Data Structure And Algorithm", price: 1666.6), 117 | ] 118 | 119 | // NSDictionary\NSMutableDictionary 120 | var dogs: [String: Any]? = [ 121 | "dog0": Dog(name: "Wang", age: 5), 122 | "dog1": Dog(name: "ErHa", age: 3), 123 | ] 124 | } 125 | 126 | write(Person(), to: file) 127 | let person = read(Person.self, from: file) 128 | XCTAssert(person?.books?.count == 2) 129 | XCTAssert(person?.dogs?.count == 2) 130 | let personString = "\(person as Any)" 131 | XCTAssert(personString.contains("Fast C++")) 132 | XCTAssert(personString.contains("Data Structure And Algorithm")) 133 | XCTAssert(personString.contains("666.6")) 134 | XCTAssert(personString.contains("1666.6")) 135 | // prevent from 66.6499999999999998 136 | XCTAssert(!personString.contains("99999")) 137 | // prevent from 66.6600000000000001 138 | XCTAssert(!personString.contains("00000")) 139 | } 140 | 141 | func testDate() { 142 | let date1 = Date(timeIntervalSince1970: timeInteval) 143 | write(date1, to: file) 144 | 145 | let date2 = read(Date.self, from: file) 146 | XCTAssert(date2 == date1) 147 | 148 | XCTAssert(read(Double.self, from: file) == timeInteval) 149 | } 150 | 151 | func testString() { 152 | let string1 = "123" 153 | write(string1, to: file) 154 | 155 | let string2 = read(String.self, from: file) 156 | XCTAssert(string2 == string1) 157 | 158 | XCTAssert(read(Int.self, from: file) == 123) 159 | } 160 | 161 | func testArray() { 162 | let array1 = ["Jack", "Rose"] 163 | write(array1, to: file) 164 | 165 | let array2 = read([String].self, from: file) 166 | XCTAssert(array2 == array1) 167 | } 168 | 169 | func testModelArray() { 170 | struct Car: Convertible { 171 | var name: String = "" 172 | var price: Double = 0.0 173 | } 174 | 175 | let models1 = [ 176 | Car(name: "BMW", price: 100.0), 177 | Car(name: "Audi", price: 70.0) 178 | ] 179 | 180 | write(models1, to: file) 181 | 182 | let models2 = read([Car].self, from: file) 183 | XCTAssert(models2?.count == models1.count) 184 | XCTAssert(models2?[0].name == "BMW") 185 | XCTAssert(models2?[0].price == 100.0) 186 | XCTAssert(models2?[1].name == "Audi") 187 | XCTAssert(models2?[1].price == 70.0) 188 | } 189 | 190 | func testModelSet() { 191 | struct Car: Convertible, Hashable { 192 | var name: String = "" 193 | var price: Double = 0.0 194 | } 195 | 196 | let models1: Set = [ 197 | Car(name: "BMW", price: 100.0), 198 | Car(name: "Audi", price: 70.0) 199 | ] 200 | 201 | write(models1, to: file) 202 | 203 | let models2 = read(Set.self, from: file)! 204 | 205 | XCTAssert(models2.count == models1.count) 206 | for car in models2 { 207 | XCTAssert(["BMW", "Audi"].contains(car.name)) 208 | XCTAssert([100.0, 70.0].contains(car.price)) 209 | } 210 | } 211 | 212 | func testModelDictionary() { 213 | struct Car: Convertible { 214 | var name: String = "" 215 | var price: Double = 0.0 216 | } 217 | 218 | let models1 = [ 219 | "car0": Car(name: "BMW", price: 100.0), 220 | "car1": Car(name: "Audi", price: 70.0) 221 | ] 222 | 223 | write(models1, to: file) 224 | 225 | let models2 = read([String: Car].self, from: file) 226 | XCTAssert(models2?.count == models1.count) 227 | 228 | let car0 = models2?["car0"] 229 | XCTAssert(car0?.name == "BMW") 230 | XCTAssert(car0?.price == 100.0) 231 | 232 | let car1 = models2?["car1"] 233 | XCTAssert(car1?.name == "Audi") 234 | XCTAssert(car1?.price == 70.0) 235 | } 236 | 237 | func checkCoding(_ type: M.Type) { 238 | let obj1 = type.init() 239 | // write to file 240 | write(obj1, to: file) 241 | 242 | // read from file 243 | let obj2 = read(type, from: file) 244 | XCTAssert(obj1 == obj2) 245 | 246 | let objString = "\(obj2 as Any)" 247 | // prevent from 66.6499999999999998 248 | XCTAssert(!objString.contains("99999")) 249 | // prevent from 66.6600000000000001 250 | XCTAssert(!objString.contains("00000")) 251 | } 252 | 253 | static var allTests = [ 254 | "testModel": testModel, 255 | "testOptional": testOptional, 256 | "testNested": testNested, 257 | "testAny": testAny, 258 | "testDate": testDate, 259 | "testString": testString, 260 | "testArray": testArray, 261 | "testModelArray": testModelArray, 262 | "testModelSet": testModelSet, 263 | "testModelDictionary": testModelDictionary 264 | ] 265 | } 266 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/Other/Global.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Global.swift 3 | // KakaJSONTests 4 | // 5 | // Created by MJ Lee on 2019/8/11. 6 | // Copyright © 2019 MJ Lee. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KakaJSON 11 | 12 | class TestGlobal: XCTestCase { 13 | struct Cat: Convertible { 14 | var age: Int = 0 15 | var name: String = "" 16 | } 17 | 18 | func testMetadata() { 19 | struct Cat { 20 | var age: Int = 0 21 | let name: String = "" 22 | } 23 | 24 | guard let type = Metadata.type(Cat.self) as? ModelType, 25 | let properties = type.properties else { return } 26 | 27 | for property in properties { 28 | print(property.name, 29 | property.type, 30 | property.isVar, 31 | property.ownerType, 32 | property.offset) 33 | // age Int true Cat 0 34 | // name String false Cat 8 35 | } 36 | } 37 | 38 | func testValue() { 39 | let string1 = "1565922866" 40 | XCTAssert(Values.int(string1) == 1565922866) 41 | XCTAssert(Values.double(string1) == 1565922866) 42 | XCTAssert(Values.cgFloat(string1) == 1565922866) 43 | let date = Values.value(string1, of: Date.self) 44 | XCTAssert(date?.timeIntervalSince1970 == 1565922866) 45 | 46 | let string2 = "true" 47 | XCTAssert(Values.bool(string2) == true) 48 | XCTAssert(Values.int(string2) == 1) 49 | } 50 | 51 | func testJSON_To_Model() { 52 | let name = "Miaomiao" 53 | let age = 26 54 | let json: [String: Any] = [ 55 | "age": age, 56 | "name": name 57 | ] 58 | let jsonString = """ 59 | { 60 | "name": "\(name)", 61 | "age": \(age) 62 | } 63 | """ 64 | let jsonData = jsonString.data(using: .utf8)! 65 | 66 | let cat1 = model(from: json, Cat.self) 67 | XCTAssert(cat1.name == name) 68 | XCTAssert(cat1.age == age) 69 | 70 | let cat2 = model(from: json, type: Cat.self) as? Cat 71 | XCTAssert(cat2?.name == name) 72 | XCTAssert(cat2?.age == age) 73 | 74 | let cat3 = model(from: jsonString, Cat.self) 75 | XCTAssert(cat3?.name == name) 76 | XCTAssert(cat3?.age == age) 77 | 78 | let cat4 = model(from: jsonString, type: Cat.self) as? Cat 79 | XCTAssert(cat4?.name == name) 80 | XCTAssert(cat4?.age == age) 81 | 82 | let cat5 = model(from: jsonData, Cat.self) 83 | XCTAssert(cat5?.name == name) 84 | XCTAssert(cat5?.age == age) 85 | 86 | let cat6 = model(from: jsonData, type: Cat.self) as? Cat 87 | XCTAssert(cat6?.name == name) 88 | XCTAssert(cat6?.age == age) 89 | } 90 | 91 | func testModel_To_JSON() { 92 | let cat = Cat(age: 26, name: "Miaomiao") 93 | 94 | let json = JSONObject(from: cat) 95 | XCTAssert(json["name"] as? String == cat.name) 96 | XCTAssert(json["age"] as? Int == cat.age) 97 | 98 | let jsonString = JSONString(from: cat) 99 | XCTAssert(jsonString.contains("\"age\":\(cat.age)") == true) 100 | XCTAssert(jsonString.contains("\"name\":\"\(cat.name)\"") == true) 101 | } 102 | 103 | func testJSONArray_To_ModelArray() { 104 | let name = "Miaomiao" 105 | let age = 26 106 | let json: [[String: Any]] = [ 107 | ["age": age, "name": name], 108 | ["age": age, "name": name] 109 | ] 110 | let jsonString = """ 111 | [ 112 | {"name": "\(name)", "age": \(age)}, 113 | {"name": "\(name)", "age": \(age)} 114 | ] 115 | """ 116 | 117 | let cats1 = modelArray(from: json, Cat.self) 118 | XCTAssert(cats1[0].name == name) 119 | XCTAssert(cats1[0].age == age) 120 | XCTAssert(cats1[1].name == name) 121 | XCTAssert(cats1[1].age == age) 122 | 123 | let cats2 = modelArray(from: json, type: Cat.self) as? [Cat] 124 | XCTAssert(cats2?[0].name == name) 125 | XCTAssert(cats2?[0].age == age) 126 | XCTAssert(cats2?[1].name == name) 127 | XCTAssert(cats2?[1].age == age) 128 | 129 | let cats3 = modelArray(from: jsonString, Cat.self) 130 | XCTAssert(cats3[0].name == name) 131 | XCTAssert(cats3[0].age == age) 132 | XCTAssert(cats3[1].name == name) 133 | XCTAssert(cats3[1].age == age) 134 | 135 | let cats4 = modelArray(from: jsonString, type: Cat.self) as? [Cat] 136 | XCTAssert(cats4?[0].name == name) 137 | XCTAssert(cats4?[0].age == age) 138 | XCTAssert(cats4?[1].name == name) 139 | XCTAssert(cats4?[1].age == age) 140 | } 141 | 142 | func testModelArray_To_JSONArray() { 143 | let cat = Cat(age: 26, name: "Miaomiao") 144 | let cats = [cat, cat] 145 | 146 | let json = JSONObjectArray(from: cats) 147 | XCTAssert(json[0]["name"] as? String == cat.name) 148 | XCTAssert(json[0]["age"] as? Int == cat.age) 149 | XCTAssert(json[1]["name"] as? String == cat.name) 150 | XCTAssert(json[1]["age"] as? Int == cat.age) 151 | 152 | let jsonString = JSONString(from: cats) 153 | XCTAssert(jsonString.contains("[") == true) 154 | XCTAssert(jsonString.contains("},{") == true) 155 | XCTAssert(jsonString.contains("]") == true) 156 | XCTAssert(jsonString.contains("\"age\":\(cat.age)") == true) 157 | XCTAssert(jsonString.contains("\"name\":\"\(cat.name)\"") == true) 158 | } 159 | 160 | static var allTests = [ 161 | "testMetadata": testMetadata, 162 | "testValue": testValue, 163 | "testJSON_To_Model": testJSON_To_Model, 164 | "testModel_To_JSON": testModel_To_JSON, 165 | "testJSONArray_To_ModelArray": testJSONArray_To_ModelArray, 166 | "testModelArray_To_JSONArray": testModelArray_To_JSONArray 167 | ] 168 | } 169 | 170 | let timeIntevalInt: Int = 1565922866 171 | let timeIntevalFloat = Float(timeIntevalInt) 172 | let timeInteval = Double(timeIntevalInt) 173 | let timeIntevalString = "\(timeIntevalInt)" 174 | let time = Date(timeIntervalSince1970: timeInteval) 175 | // 16 decimals 176 | let longDoubleString = "0.1234567890123456" 177 | let longDouble: Double = 0.1234567890123456 178 | // 8 decimals 179 | let longFloatString = "0.12345678" 180 | let longFloat: Float = 0.12345678 181 | // 39 decimals 182 | let longDecimalString = "0.123456789012345678901234567890123456789" 183 | var longDecimal = Decimal(string: longDecimalString)! 184 | var longDecimalNumber = NSDecimalNumber(string: longDecimalString) 185 | 186 | func checkModelToJSon(_ type: M.Type) { 187 | // create model 188 | let model = type.init() 189 | // get JSONString from model 190 | let jsonString = model.kj.JSONString() 191 | 192 | // check JSON and JSONString 193 | let modelFromJsonString = jsonString.kj.model(type: type) as? M 194 | XCTAssert(model == modelFromJsonString) 195 | 196 | // prevent from 66.6499999999999998 197 | XCTAssert(jsonString.contains("99999") == false) 198 | // prevent from 66.6600000000000001 199 | XCTAssert(jsonString.contains("00000") == false) 200 | } 201 | 202 | /// note 203 | /* 204 | protocol Runnable {} 205 | class Dog: Runnable {} 206 | 207 | print("--------section0--------") 208 | print(Runnable.self is Runnable.Type) // false 209 | 210 | print("--------section1--------") 211 | let dog1: Dog = Dog() 212 | print(dog1 as? Runnable) // success 213 | print(dog1 is Runnable) // true 214 | print(dog1 as? Dog) // success 215 | print(dog1 is Dog) // true 216 | 217 | print("--------section2--------") 218 | let dog2 = Dog.self 219 | print(dog2 as? Runnable.Type) // success 220 | print(dog2 is Runnable.Type) // true 221 | print(dog2 as? Dog.Type) // success 222 | print(dog2 is Dog.Type) // true 223 | 224 | print("--------section3--------") 225 | let dog3: dog?? = Dog() 226 | print(dog3 as? Runnable) // success 227 | print(dog3 is Runnable) // true 228 | print(dog3 as? Dog) // success 229 | print(dog3 is Dog) // true 230 | 231 | print("--------section4--------") 232 | let dog4: dog?? = nil 233 | print(dog4 as? Runnable) // nil 234 | print(dog4 is Runnable) // false 235 | print(dog4 as? Dog) // nil 236 | print(dog4 is Dog) // false 237 | 238 | print("--------section5--------") 239 | let dog5 = dog??.self 240 | print(dog5 as? Runnable.Type) // nil 241 | print(dog5 is Runnable.Type) // false 242 | print(dog5 as? Dog.Type) // nil 243 | print(dog5 is Dog.Type) // false 244 | 245 | print("--------section6--------") 246 | let dog6: Dog = Dog() 247 | print(dog6 as? Runnable???) // success 248 | print(dog6 is Runnable???) // false 249 | print(dog6 as? dog??) // success 250 | print(dog6 is dog??) // false 251 | 252 | print("--------section7--------") 253 | let dog7 = Dog.self 254 | print(dog7 as? Runnable???.Type) // nil 255 | print(dog7 is Runnable???.Type) // false 256 | print(dog7 as? dog??.Type) // nil 257 | print(dog7 is dog??.Type) // false 258 | */ 259 | 260 | /* 261 | class Runnable {} 262 | class Dog: Runnable {} 263 | 264 | print("--------section0--------") 265 | print(Runnable.self is Runnable.Type) // true 266 | 267 | print("--------section1--------") 268 | let dog1: Dog = Dog() 269 | print(dog1 as? Runnable) // success 270 | print(dog1 is Runnable) // true 271 | print(dog1 as? Dog) // success 272 | print(dog1 is Dog) // true 273 | 274 | print("--------section2--------") 275 | let dog2 = Dog.self 276 | print(dog2 as? Runnable.Type) // success 277 | print(dog2 is Runnable.Type) // true 278 | print(dog2 as? Dog.Type) // success 279 | print(dog2 is Dog.Type) // true 280 | 281 | print("--------section3--------") 282 | let dog3: dog?? = Dog() 283 | print(dog3 as? Runnable) // success 284 | print(dog3 is Runnable) // true 285 | print(dog3 as? Dog) // success 286 | print(dog3 is Dog) // true 287 | 288 | print("--------section4--------") 289 | let dog4: dog?? = nil 290 | print(dog4 as? Runnable) // nil 291 | print(dog4 is Runnable) // false 292 | print(dog4 as? Dog) // nil 293 | print(dog4 is Dog) // false 294 | 295 | print("--------section5--------") 296 | let dog5 = dog??.self 297 | print(dog5 as? Runnable.Type) // nil 298 | print(dog5 is Runnable.Type) // false 299 | print(dog5 as? Dog.Type) // nil 300 | print(dog5 is Dog.Type) // false 301 | 302 | print("--------section6--------") 303 | let dog6: Dog = Dog() 304 | print(dog6 as? Runnable???) // success 305 | print(dog6 is Runnable???) // false 306 | print(dog6 as? dog??) // success 307 | print(dog6 is dog??) // false 308 | 309 | print("--------section7--------") 310 | let dog7 = Dog.self 311 | print(dog7 as? Runnable???.Type) // nil 312 | print(dog7 is Runnable???.Type) // false 313 | print(dog7 as? dog??.Type) // nil 314 | print(dog7 is dog??.Type) // false 315 | */ 316 | 317 | /* 318 | let array: [Int?] = [1, nil, 2] 319 | print(array as? [Any]) // success 320 | print(array as? [Int]) // nil 321 | */ 322 | -------------------------------------------------------------------------------- /Tests/KakaJSONTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(JTM_01_Basic.allTests), 7 | testCase(JTM_02_DataType.allTests), 8 | testCase(JTM_03_NestedModel.allTests), 9 | testCase(JTM_04_ModelArray.allTests), 10 | testCase(JTM_05_KeyMapping.allTests), 11 | testCase(JTM_06_CustomValue.allTests), 12 | testCase(JTM_07_DynamicModel.allTests), 13 | testCase(MTJ_01_Basic.allTests), 14 | testCase(MTJ_02_NestedModel.allTests), 15 | testCase(MTJ_03_ModelArray.allTests), 16 | testCase(MTJ_04_KeyMapping.allTests), 17 | testCase(MTJ_05_CustomValue.allTests), 18 | testCase(TestCoding.allTests), 19 | testCase(TestGlobal.allTests), 20 | testCase(TestBugs.allTests) 21 | ] 22 | } 23 | #endif 24 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import KakaJSONTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += KakaJSONTests.allTests() 7 | XCTMain(tests) 8 | --------------------------------------------------------------------------------