├── .travis.yml ├── .gitignore ├── scripts └── ci.sh ├── LICENSE ├── HandyJSONTests ├── Info-macOS.plist ├── Info-tvOS.plist ├── Info-iOS.plist ├── MemoryLayoutValidationTest.swift ├── InvalidStateHandlingTest.swift ├── ObjectiveCObjectTest.swift ├── CustomMappingTest.swift ├── NestObjectTest.swift ├── SerializeToSimpleObjectTest.swift ├── StructObjectTest.swift ├── AllBaseTypePropertyObjectTest.swift ├── SerializeToJSONTests.swift └── ClassObjectTest.swift ├── HandyJSON ├── Info-tvOS.plist ├── Info-watchOS.plist ├── Info-iOS.plist ├── Info-macOS.plist ├── HandyJSON.h ├── Extension.swift ├── HelpingMapper.swift ├── JSONDeserializer.swift ├── Converter.swift ├── JSONSerializer.swift └── Property.swift ├── HandyJSON.podspec ├── HandyJSONDemo ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.storyboard ├── AppDelegate.swift └── ViewController.swift ├── HandyJSON.xcodeproj └── xcshareddata │ └── xcschemes │ ├── HandyJSON macOS Tests.xcscheme │ ├── HandyJSON tvOS Tests.xcscheme │ ├── HandyJSON iOS Tests.xcscheme │ ├── HandyJSON watchOS.xcscheme │ ├── HandyJSONDemo.xcscheme │ ├── HandyJSON tvOS.xcscheme │ ├── HandyJSON macOS.xcscheme │ └── HandyJSON iOS.xcscheme ├── CHANGELOG.md ├── README_cn.md └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8 3 | 4 | before_install: 5 | - rvm get head 6 | script: 7 | - sh scripts/ci.sh 8 | after_success: 9 | - bash <(curl -s https://codecov.io/bash) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | .idea/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.xcuserstate 17 | *.xcworkspace 18 | Pods 19 | -------------------------------------------------------------------------------- /scripts/ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | xcodebuild -scheme "HandyJSON iOS Tests" -destination "platform=iOS Simulator,name=iPhone 6" test 6 | 7 | xcodebuild -scheme "HandyJSON macOS" test 8 | 9 | xcodebuild -scheme "HandyJSON tvOS" -destination "platform=tvOS Simulator,name=Apple TV 1080p" test 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 1999-2016 Alibaba Group Holding Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /HandyJSONTests/Info-macOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /HandyJSONTests/Info-tvOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /HandyJSONTests/Info-iOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /HandyJSON/Info-tvOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /HandyJSON/Info-watchOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /HandyJSON.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.author = {'xuyecan' => 'xuyecan@gmail.com'} 4 | s.license = 'Apache License 2.0' 5 | s.requires_arc = true 6 | s.version = '1.3.0' 7 | s.homepage = "https://github.com/alibaba/handyjson" 8 | s.name = "HandyJSON" 9 | 10 | s.source_files = 'HandyJSON/**/*.{swift,h,m}' 11 | s.source = { :git => 'https://github.com/alibaba/HandyJSON.git', :tag => s.version.to_s } 12 | 13 | s.summary = 'A Json Serialization & Deserialization Library for Swift' 14 | s.description = 'A Handy Json Library for Swift which serials object to json and deserials json to object' 15 | 16 | s.ios.deployment_target = '8.0' 17 | s.osx.deployment_target = '10.9' 18 | s.watchos.deployment_target = '2.0' 19 | s.tvos.deployment_target = '9.0' 20 | 21 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '3.0' } 22 | end 23 | -------------------------------------------------------------------------------- /HandyJSON/Info-iOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /HandyJSON/Info-macOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2016年 aliyun. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /HandyJSONDemo/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 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /HandyJSON/HandyJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 7/11/16. 18 | // 19 | 20 | @import Foundation; 21 | 22 | //! Project version number for HandyJSON. 23 | FOUNDATION_EXPORT double HandyJSONVersionNumber; 24 | 25 | //! Project version string for HandyJSON. 26 | FOUNDATION_EXPORT const unsigned char HandyJSONVersionString[]; 27 | 28 | // In this header, you should import all the public headers of your framework using statements like #import 29 | 30 | 31 | -------------------------------------------------------------------------------- /HandyJSONDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /HandyJSONDemo/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 | 26 | -------------------------------------------------------------------------------- /HandyJSON/Extension.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 7/7/16. 18 | // 19 | 20 | import Foundation 21 | 22 | /* 23 | 24 | Int, 25 | Int8, 26 | Int16, 27 | Int32, 28 | Int64, 29 | UInt, 30 | UInt8, 31 | UInt16, 32 | UInt32, 33 | UInt64, 34 | Bool, 35 | Float, 36 | Double, 37 | String, 38 | */ 39 | 40 | extension Int: BasePropertyProtocol {} 41 | extension Int8: BasePropertyProtocol {} 42 | extension Int16: BasePropertyProtocol {} 43 | extension Int32: BasePropertyProtocol {} 44 | extension Int64: BasePropertyProtocol {} 45 | extension UInt: BasePropertyProtocol {} 46 | extension UInt8: BasePropertyProtocol {} 47 | extension UInt16: BasePropertyProtocol {} 48 | extension UInt32: BasePropertyProtocol {} 49 | extension UInt64: BasePropertyProtocol {} 50 | extension Bool: BasePropertyProtocol {} 51 | extension Float: BasePropertyProtocol {} 52 | extension Double: BasePropertyProtocol {} 53 | extension String: BasePropertyProtocol {} 54 | 55 | extension NSString: BasePropertyProtocol {} 56 | extension NSNumber: BasePropertyProtocol {} 57 | 58 | -------------------------------------------------------------------------------- /HandyJSONDemo/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 | 27 | 28 | -------------------------------------------------------------------------------- /HandyJSON/HelpingMapper.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 9/20/16. 18 | // 19 | 20 | import Foundation 21 | 22 | public class HelpingMapper { 23 | 24 | private var mapping = [Int: (String?, ((String) -> ())?)]() 25 | 26 | public func specify(property: inout T, name: String) { 27 | let key = withUnsafePointer(to: &property, { return $0 }).hashValue 28 | self.mapping[key] = (name, nil) 29 | } 30 | 31 | public func specify(property: inout T, converter: @escaping (String) -> T) { 32 | let pointer = withUnsafePointer(to: &property, { return $0 }) 33 | let key = pointer.hashValue 34 | let assign = { (rawValue: String) in 35 | UnsafeMutablePointer(mutating: pointer).pointee = converter(rawValue) 36 | } 37 | self.mapping[key] = (nil, assign) 38 | } 39 | 40 | public func specify(property: inout T, name: String, converter: @escaping (String) -> T) { 41 | let pointer = withUnsafePointer(to: &property, { return $0 }) 42 | let key = pointer.hashValue 43 | let assign = { (rawValue: String) in 44 | UnsafeMutablePointer(mutating: pointer).pointee = converter(rawValue) 45 | } 46 | self.mapping[key] = (name, assign) 47 | } 48 | 49 | internal func getNameAndConverter(key: Int) -> (String?, ((String) -> ())?)? { 50 | return mapping[key] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /HandyJSONTests/MemoryLayoutValidationTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 8/9/16. 18 | // 19 | 20 | import XCTest 21 | 22 | class MemoryLayoutValidationTest: XCTestCase { 23 | 24 | override func setUp() { 25 | super.setUp() 26 | // Put setup code here. This method is called before the invocation of each test method in the class. 27 | } 28 | 29 | override func tearDown() { 30 | // Put teardown code here. This method is called after the invocation of each test method in the class. 31 | super.tearDown() 32 | } 33 | 34 | func testHeadOfClass() { 35 | class A { 36 | var m1: Int8 = 0 37 | } 38 | 39 | let a = A() 40 | let basePtr = Unmanaged.passUnretained(a).toOpaque().advanced(by: 8 + MemoryLayout.size) 41 | let realPtr = UnsafeMutablePointer(bitPattern: basePtr.hashValue) 42 | realPtr?.pointee = 11 43 | XCTAssert(a.m1 == 11) 44 | } 45 | 46 | func testHeadOfStruct() { 47 | struct B { 48 | var m1: Int8 = 0 49 | } 50 | 51 | var b = B() 52 | let basePtr = UnsafePointer(bitPattern: withUnsafePointer(to: &b, { 53 | return UnsafeRawPointer($0).bindMemory(to: Int8.self, capacity: MemoryLayout.stride) 54 | }).hashValue) 55 | let realPtr = UnsafeMutablePointer(bitPattern: basePtr?.hashValue ?? 0) 56 | realPtr?.pointee = 11 57 | XCTAssert(b.m1 == 11) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /HandyJSON.xcodeproj/xcshareddata/xcschemes/HandyJSON macOS Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /HandyJSON.xcodeproj/xcshareddata/xcschemes/HandyJSON tvOS Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /HandyJSON.xcodeproj/xcshareddata/xcschemes/HandyJSON iOS Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 40 | 41 | 42 | 43 | 49 | 50 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /HandyJSONTests/InvalidStateHandlingTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 8/9/16. 18 | // 19 | 20 | import XCTest 21 | import HandyJSON 22 | 23 | class InvalidStateHandlingTest: XCTestCase { 24 | 25 | override func setUp() { 26 | super.setUp() 27 | // Put setup code here. This method is called before the invocation of each test method in the class. 28 | } 29 | 30 | override func tearDown() { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | super.tearDown() 33 | } 34 | 35 | func testDeserializeFromInvalidJSONString() { 36 | class A: HandyJSON { 37 | var name: String? 38 | var id: String? 39 | var height: Int? 40 | 41 | required init() {} 42 | } 43 | 44 | let jsonString = "{\"name\"\"Bob\",\"id\":\"12345\",\"height\":180}" 45 | let a = JSONDeserializer.deserializeFrom(json: jsonString) 46 | XCTAssertNil(a) 47 | let b = JSONDeserializer.deserializeModelArrayFrom(json: jsonString) 48 | XCTAssertNil(b) 49 | } 50 | 51 | func testDeserializeByIncorrectDesignatedPath() { 52 | class B: HandyJSON { 53 | var name: String? 54 | var id: String? 55 | var height: Int? 56 | 57 | required init() {} 58 | } 59 | 60 | let jsonString = "{\"name\":\"Bob\",\"id\":\"12345\",\"height\":180}" 61 | let a = JSONDeserializer.deserializeFrom(json: jsonString, designatedPath: "wrong") 62 | XCTAssertNil(a) 63 | let b = JSONDeserializer.deserializeFrom(json: jsonString, designatedPath: "name.name") 64 | XCTAssertNil(b) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /HandyJSONTests/ObjectiveCObjectTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 9/20/16. 18 | // 19 | 20 | import XCTest 21 | import HandyJSON 22 | 23 | class ObjectiveCObjectTest: XCTestCase { 24 | 25 | override func setUp() { 26 | super.setUp() 27 | // Put setup code here. This method is called before the invocation of each test method in the class. 28 | } 29 | 30 | override func tearDown() { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | super.tearDown() 33 | } 34 | 35 | func testSimpleClass() { 36 | class A: HandyJSON { 37 | var name: NSString? 38 | var id: NSString? 39 | var height: NSNumber? 40 | 41 | required init() {} 42 | } 43 | 44 | let jsonString = "{\"name\":\"Bob\",\"id\":\"12345\",\"height\":180}" 45 | let a = JSONDeserializer.deserializeFrom(json: jsonString)! 46 | XCTAssert(a.name == "Bob") 47 | XCTAssert(a.id == "12345") 48 | XCTAssert(a.height == 180) 49 | } 50 | 51 | func testClassWithArrayProperty() { 52 | class B: HandyJSON { 53 | var arr1: NSArray? 54 | var arr2: NSArray? 55 | var id: Int? 56 | 57 | required init() {} 58 | } 59 | 60 | let jsonString = "{\"id\":123456,\"arr1\":[1,2,3,4,5,6],\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 61 | let b = JSONDeserializer.deserializeFrom(json: jsonString)! 62 | XCTAssert(b.id == 123456) 63 | XCTAssert(b.arr1?.count == 6) 64 | XCTAssert(b.arr2?.count == 5) 65 | XCTAssert((b.arr1?.object(at: 5) as? NSNumber)?.intValue == 6) 66 | XCTAssert((b.arr2?.object(at: 4) as? NSString)?.isEqual(to: "e") == true) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /HandyJSONDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 7/11/16. 18 | // 19 | 20 | import UIKit 21 | 22 | @UIApplicationMain 23 | class AppDelegate: UIResponder, UIApplicationDelegate { 24 | 25 | var window: UIWindow? 26 | 27 | 28 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 29 | // Override point for customization after application launch. 30 | return true 31 | } 32 | 33 | func applicationWillResignActive(_ application: UIApplication) { 34 | // 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. 35 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 36 | } 37 | 38 | func applicationDidEnterBackground(_ application: UIApplication) { 39 | // 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. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | func applicationWillEnterForeground(_ application: UIApplication) { 44 | // 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. 45 | } 46 | 47 | func applicationDidBecomeActive(_ application: UIApplication) { 48 | // 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. 49 | } 50 | 51 | func applicationWillTerminate(_ application: UIApplication) { 52 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 53 | } 54 | 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /HandyJSON.xcodeproj/xcshareddata/xcschemes/HandyJSON watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /HandyJSONTests/CustomMappingTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // 18 | // CustomMappingTest.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 9/27/16. 22 | // 23 | 24 | import XCTest 25 | import HandyJSON 26 | 27 | class CustomMappingTest: XCTestCase { 28 | 29 | override func setUp() { 30 | super.setUp() 31 | // Put setup code here. This method is called before the invocation of each test method in the class. 32 | } 33 | 34 | override func tearDown() { 35 | // Put teardown code here. This method is called after the invocation of each test method in the class. 36 | super.tearDown() 37 | } 38 | 39 | func testStructMapping() { 40 | 41 | struct A: HandyJSON { 42 | var name: String? 43 | var id: String? 44 | var height: Int? 45 | 46 | mutating func mapping(mapper: HelpingMapper) { 47 | // specify json field name 48 | mapper.specify(property: &name, name: "json_name") 49 | 50 | // specify converting method 51 | mapper.specify(property: &id, converter: { rawValue -> String in 52 | return "json_" + rawValue 53 | }) 54 | 55 | // specify both 56 | mapper.specify(property: &height, name: "json_height", converter: { rawValue -> Int in 57 | print("classMapping: ", rawValue) 58 | return Int(rawValue) ?? 0 59 | }) 60 | } 61 | } 62 | 63 | let jsonString = "{\"json_name\":\"Bob\",\"id\":\"12345\",\"json_height\":180}" 64 | let a = JSONDeserializer.deserializeFrom(json: jsonString)! 65 | XCTAssert(a.name == "Bob") 66 | XCTAssert(a.id == "json_12345") 67 | XCTAssert(a.height == 180) 68 | } 69 | 70 | func testClassMapping() { 71 | 72 | class A: HandyJSON { 73 | var name: String? 74 | var id: String? 75 | var height: Int? 76 | 77 | required init() {} 78 | 79 | func mapping(mapper: HelpingMapper) { 80 | // specify json field name 81 | mapper.specify(property: &name, name: "json_name") 82 | 83 | // specify converting method 84 | mapper.specify(property: &id, converter: { rawValue -> String in 85 | return "json_" + rawValue 86 | }) 87 | 88 | // specify both 89 | mapper.specify(property: &height, name: "json_height", converter: { rawValue -> Int? in 90 | print("classMapping: ", rawValue) 91 | return Int(rawValue) 92 | }) 93 | } 94 | } 95 | 96 | let jsonString = "{\"json_name\":\"Bob\",\"id\":\"12345\",\"json_height\":180}" 97 | let a = JSONDeserializer.deserializeFrom(json: jsonString)! 98 | XCTAssert(a.name == "Bob") 99 | XCTAssert(a.id == "json_12345") 100 | XCTAssert(a.height == 180) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /HandyJSON.xcodeproj/xcshareddata/xcschemes/HandyJSONDemo.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 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /HandyJSON.xcodeproj/xcshareddata/xcschemes/HandyJSON tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /HandyJSON.xcodeproj/xcshareddata/xcschemes/HandyJSON macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /HandyJSON.xcodeproj/xcshareddata/xcschemes/HandyJSON iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /HandyJSONTests/NestObjectTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 8/8/16. 18 | // 19 | 20 | import XCTest 21 | import HandyJSON 22 | 23 | class NestObjectTest: XCTestCase { 24 | 25 | override func setUp() { 26 | super.setUp() 27 | // Put setup code here. This method is called before the invocation of each test method in the class. 28 | } 29 | 30 | override func tearDown() { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | super.tearDown() 33 | } 34 | 35 | enum Gender: String, HandyJSONEnum { 36 | case Male = "Male" 37 | case Female = "Female" 38 | 39 | static func makeInitWrapper() -> InitWrapperProtocol? { 40 | return InitWrapper(rawInit: Gender.init) 41 | } 42 | } 43 | 44 | class Teacher: HandyJSON { 45 | var name: String? 46 | var age: Int? 47 | var height: Int? 48 | var gender: Gender? 49 | 50 | required init() {} 51 | } 52 | 53 | struct Subject: HandyJSON { 54 | var name: String? 55 | var id: Int64? 56 | var credit: Int? 57 | var lessonPeriod: Int? 58 | } 59 | 60 | class Student: HandyJSON { 61 | var id: String? 62 | var name: String? 63 | var age: Int? 64 | var height: Int? 65 | var gender: Gender? 66 | var className: String? 67 | var teacher: Teacher? 68 | var subject: [Subject]? 69 | var seat: String? 70 | 71 | required init() {} 72 | } 73 | 74 | func testNormalNestObject() { 75 | /** 76 | { 77 | "id": "77544", 78 | "name": "Tom Li", 79 | "age": 18, 80 | "height": 180, 81 | "gender": "Male", 82 | "className": "A", 83 | "teacher": { 84 | "name": "Lucy He", 85 | "age": 28, 86 | "height": 172, 87 | "gender": "Female", 88 | }, 89 | "subject": [ 90 | { 91 | "name": "math", 92 | "id": 18000324583, 93 | "credit": 4, 94 | "lessonPeriod": 48 95 | }, 96 | { 97 | "name": "computer", 98 | "id": 18000324584, 99 | "credit": 8, 100 | "lessonPeriod": 64 101 | } 102 | ], 103 | "seat": "4-3-23" 104 | } 105 | **/ 106 | let jsonString = "{\"id\":\"77544\",\"name\":\"Tom Li\",\"age\":18,\"height\":180,\"gender\":\"Male\",\"className\":\"A\",\"teacher\":{\"name\":\"Lucy He\",\"age\":28,\"height\":172,\"gender\":\"Female\",},\"subject\":[{\"name\":\"math\",\"id\":18000324583,\"credit\":4,\"lessonPeriod\":48},{\"name\":\"computer\",\"id\":18000324584,\"credit\":8,\"lessonPeriod\":64}],\"seat\":\"4-3-23\"}" 107 | let student = JSONDeserializer.deserializeFrom(json: jsonString)! 108 | XCTAssert(student.id == "77544") 109 | XCTAssert(student.name == "Tom Li") 110 | XCTAssert(student.age == 18) 111 | XCTAssert(student.height == 180) 112 | XCTAssert(student.gender == .Male) 113 | XCTAssert(student.className == "A") 114 | XCTAssert(student.teacher?.name == "Lucy He") 115 | XCTAssert(student.teacher?.age == 28) 116 | XCTAssert(student.teacher?.height == 172) 117 | XCTAssert(student.teacher?.gender == .Female) 118 | XCTAssert(student.subject?.first?.name == "math") 119 | XCTAssert(student.subject?.first?.id == 18000324583) 120 | XCTAssert(student.subject?.first?.credit == 4) 121 | XCTAssert(student.subject?.first?.lessonPeriod == 48) 122 | XCTAssert(student.subject?.last?.name == "computer") 123 | XCTAssert(student.subject?.last?.id == 18000324584) 124 | XCTAssert(student.subject?.last?.credit == 8) 125 | XCTAssert(student.subject?.last?.lessonPeriod == 64) 126 | XCTAssert(student.seat == "4-3-23") 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /HandyJSONDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 7/11/16. 18 | 19 | import UIKit 20 | import HandyJSON 21 | 22 | enum Grade: Int { 23 | case One = 1 24 | case Two = 2 25 | case Three = 3 26 | } 27 | 28 | extension Grade: HandyJSONEnum { 29 | static func makeInitWrapper() -> InitWrapperProtocol? { 30 | return InitWrapper(rawInit: Grade.init) 31 | } 32 | } 33 | 34 | class ViewController: UIViewController { 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | // Do any additional setup after loading the view, typically from a nib. 39 | 40 | self.serialization() 41 | self.deserialization() 42 | } 43 | 44 | override func didReceiveMemoryWarning() { 45 | super.didReceiveMemoryWarning() 46 | // Dispose of any resources that can be recreated. 47 | } 48 | 49 | func serialization() { 50 | enum Gender: String { 51 | case Male = "Male" 52 | case Female = "Female" 53 | } 54 | 55 | struct Subject { 56 | var id: Int64? 57 | var name: String? 58 | 59 | init(id: Int64, name: String) { 60 | self.id = id 61 | self.name = name 62 | } 63 | } 64 | 65 | class Student { 66 | var name: String? 67 | var gender: Gender? 68 | var subjects: [Subject]? 69 | } 70 | 71 | let student = Student() 72 | student.name = "Jack" 73 | student.gender = .Female 74 | student.subjects = [Subject(id: 1, name: "Math"), Subject(id: 2, name: "English"), Subject(id: 3, name: "Philosophy")] 75 | print(JSONSerializer.serializeToJSON(object: student)!) 76 | print(JSONSerializer.serializeToJSON(object: student, prettify: true)!) 77 | 78 | let nsDict: NSDictionary = ["a": 1, "b": [1, 2, 3], "c": "hello"] 79 | print(JSONSerializer.serialize(dict: nsDict).toJSON()!) 80 | print(JSONSerializer.serialize(dict: nsDict).toPrettifyJSON()!) 81 | 82 | let nsArray: NSArray = ["a", "b", 1, 2] 83 | print(JSONSerializer.serialize(array: nsArray).toJSON()!) 84 | print(JSONSerializer.serialize(array: nsArray).toPrettifyJSON()!) 85 | } 86 | 87 | func deserialization() { 88 | enum Gender: String, HandyJSONEnum { 89 | case Male = "Male" 90 | case Female = "Female" 91 | 92 | static func makeInitWrapper() -> InitWrapperProtocol? { 93 | return InitWrapper(rawInit: Gender.init) 94 | } 95 | } 96 | 97 | struct Teacher: HandyJSON { 98 | var name: String? 99 | var age: Int? 100 | var height: Int? 101 | var gender: Gender? 102 | } 103 | 104 | struct Subject: HandyJSON { 105 | var name: String? 106 | var id: Int64? 107 | var credit: Int? 108 | var lessonPeriod: Int? 109 | } 110 | 111 | class Student: HandyJSON { 112 | var id: String? 113 | var name: String? 114 | var age: Int? 115 | var grade: Grade = .One 116 | var height: Int? 117 | var gender: Gender? 118 | var className: String? 119 | var teacher: Teacher? 120 | var subject: [Subject]? 121 | var seat: String? 122 | 123 | required init() {} 124 | } 125 | 126 | let jsonString = "{\"id\":\"77544\",\"name\":\"Tom Li\",\"age\":18,\"grade\":2,\"height\":180,\"gender\":\"Male\",\"className\":\"A\",\"teacher\":{\"name\":\"Lucy He\",\"age\":28,\"height\":172,\"gender\":\"Female\",},\"subject\":[{\"name\":\"math\",\"id\":18000324583,\"credit\":4,\"lessonPeriod\":48},{\"name\":\"computer\",\"id\":18000324584,\"credit\":8,\"lessonPeriod\":64}],\"seat\":\"4-3-23\"}" 127 | 128 | if let student = JSONDeserializer.deserializeFrom(json: jsonString) { 129 | print("\(student)") 130 | print("\(student.grade)") 131 | print("\(student.gender)") 132 | print("\(student.subject)") 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /HandyJSON/JSONDeserializer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 7/7/16. 18 | // 19 | 20 | import Foundation 21 | 22 | public class JSONDeserializer { 23 | 24 | /// Converts a NSDictionary to Model if the properties match 25 | public static func deserializeFrom(dict: NSDictionary?) -> T? { 26 | guard let _dict = dict else { 27 | return nil 28 | } 29 | return T._transform(dict: _dict, toType: T.self) as? T 30 | } 31 | 32 | /// Finds the internal NSDictionary in `dict` as the `designatedPath` specified, and converts it to a Model 33 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer 34 | public static func deserializeFrom(dict: NSDictionary?, designatedPath: String?) -> T? { 35 | if let targetDict = self.getObject(inside: dict, by: designatedPath) as? NSDictionary { 36 | return self.deserializeFrom(dict: targetDict) 37 | } 38 | return nil 39 | } 40 | 41 | /// Converts a JSON string to Model if the properties match 42 | /// return `nil` if the string is not in valid JSON format 43 | public static func deserializeFrom(json: String?) -> T? { 44 | return self.deserializeFrom(json: json, designatedPath: nil) 45 | } 46 | 47 | /// Finds the internal JSON field in `json` as the `designatedPath` specified, and converts it to a Model 48 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer 49 | public static func deserializeFrom(json: String?, designatedPath: String?) -> T? { 50 | guard let _json = json else { 51 | return nil 52 | } 53 | do { 54 | let jsonObject = try JSONSerialization.jsonObject(with: _json.data(using: String.Encoding.utf8)!, options: .allowFragments) 55 | if let jsonDict = jsonObject as? NSDictionary { 56 | return self.deserializeFrom(dict: jsonDict, designatedPath: designatedPath) 57 | } 58 | } catch let error { 59 | print(error) 60 | } 61 | return nil 62 | } 63 | 64 | /// if `json` is representing a array, such as `[{...}, {...}, {...}]`, 65 | /// this method converts it to a Models array 66 | public static func deserializeModelArrayFrom(json: String?) -> [T?]? { 67 | return self.deserializeModelArrayFrom(json: json, designatedPath: nil) 68 | } 69 | 70 | /// if the JSON field finded by `designatedPath` in `json` is representing a array, such as `[{...}, {...}, {...}]`, 71 | /// this method converts it to a Models array 72 | public static func deserializeModelArrayFrom(json: String?, designatedPath: String?) -> [T?]? { 73 | guard let _json = json else { 74 | return nil 75 | } 76 | do { 77 | let jsonObject = try JSONSerialization.jsonObject(with: _json.data(using: String.Encoding.utf8)!, options: .allowFragments) 78 | if let jsonArray = self.getObject(inside: jsonObject as? NSObject, by: designatedPath) as? NSArray { 79 | return jsonArray.map({ (jsonDict) -> T? in 80 | return self.deserializeFrom(dict: jsonDict as? NSDictionary) 81 | }) 82 | } 83 | } catch let error { 84 | print(error) 85 | } 86 | return nil 87 | } 88 | 89 | internal static func getObject(inside jsonObject: NSObject?, by designatedPath: String?) -> NSObject? { 90 | var nodeValue: NSObject? = jsonObject 91 | var abort = false 92 | if let paths = designatedPath?.components(separatedBy: "."), paths.count > 0 { 93 | paths.forEach({ (seg) in 94 | if seg.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "" || abort { 95 | return 96 | } 97 | if let next = (nodeValue as? NSDictionary)?.object(forKey: seg) as? NSObject { 98 | nodeValue = next 99 | } else { 100 | abort = true 101 | } 102 | }) 103 | } 104 | return abort ? nil : nodeValue 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.2.1](https://github.com/alibaba/HandyJSON/tree/1.2.1) (2016-11-04) 4 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/1.2.0...1.2.1) 5 | 6 | **Closed issues:** 7 | 8 | - Xcode8.1编译报错 [\#28](https://github.com/alibaba/HandyJSON/issues/28) 9 | 10 | ## [1.2.0](https://github.com/alibaba/HandyJSON/tree/1.2.0) (2016-11-01) 11 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/1.1.0...1.2.0) 12 | 13 | **Closed issues:** 14 | 15 | - 请问下一个版本大概什么时候能发布呢? [\#27](https://github.com/alibaba/HandyJSON/issues/27) 16 | - 模型中有一个,模型数组属性。 [\#25](https://github.com/alibaba/HandyJSON/issues/25) 17 | - How can I use this handsome library on Android platform [\#24](https://github.com/alibaba/HandyJSON/issues/24) 18 | - 请问有json数组转模型数组的方法么? [\#23](https://github.com/alibaba/HandyJSON/issues/23) 19 | - Support Linux [\#22](https://github.com/alibaba/HandyJSON/issues/22) 20 | - 为什么字典转模型时 传参数是 ObjectC 的 NSDictionary 类型 而不是 Swift 的 Collection 类型 [\#21](https://github.com/alibaba/HandyJSON/issues/21) 21 | - 小问题 [\#20](https://github.com/alibaba/HandyJSON/issues/20) 22 | - Manual Installation [\#13](https://github.com/alibaba/HandyJSON/issues/13) 23 | 24 | **Merged pull requests:** 25 | 26 | - ready for 1.2.0 [\#31](https://github.com/alibaba/HandyJSON/pull/31) ([xuyecan](https://github.com/xuyecan)) 27 | - support array formal json string [\#30](https://github.com/alibaba/HandyJSON/pull/30) ([xuyecan](https://github.com/xuyecan)) 28 | - fix compile error on xcode8 [\#29](https://github.com/alibaba/HandyJSON/pull/29) ([aixinyunchou](https://github.com/aixinyunchou)) 29 | - refactor serialization to support more features [\#26](https://github.com/alibaba/HandyJSON/pull/26) ([xuyecan](https://github.com/xuyecan)) 30 | - Fix docs link [\#19](https://github.com/alibaba/HandyJSON/pull/19) ([khasinski](https://github.com/khasinski)) 31 | - Add carthage badge [\#18](https://github.com/alibaba/HandyJSON/pull/18) ([cijianzy](https://github.com/cijianzy)) 32 | - add the manually installation section [\#17](https://github.com/alibaba/HandyJSON/pull/17) ([xuyecan](https://github.com/xuyecan)) 33 | 34 | ## [1.1.0](https://github.com/alibaba/HandyJSON/tree/1.1.0) (2016-10-05) 35 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/0.4.0...1.1.0) 36 | 37 | **Merged pull requests:** 38 | 39 | - Update file directory && Fix copy bundle [\#16](https://github.com/alibaba/HandyJSON/pull/16) ([cijianzy](https://github.com/cijianzy)) 40 | - Update file directory && Fix copy bundle [\#15](https://github.com/alibaba/HandyJSON/pull/15) ([cijianzy](https://github.com/cijianzy)) 41 | 42 | ## [0.4.0](https://github.com/alibaba/HandyJSON/tree/0.4.0) (2016-10-04) 43 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/1.0.0...0.4.0) 44 | 45 | **Merged pull requests:** 46 | 47 | - Support all platform [\#14](https://github.com/alibaba/HandyJSON/pull/14) ([cijianzy](https://github.com/cijianzy)) 48 | - code formatting [\#12](https://github.com/alibaba/HandyJSON/pull/12) ([xuyecan](https://github.com/xuyecan)) 49 | - add chinese doc link [\#11](https://github.com/alibaba/HandyJSON/pull/11) ([xuyecan](https://github.com/xuyecan)) 50 | - release 1.0.0 [\#10](https://github.com/alibaba/HandyJSON/pull/10) ([xuyecan](https://github.com/xuyecan)) 51 | 52 | ## [1.0.0](https://github.com/alibaba/HandyJSON/tree/1.0.0) (2016-10-01) 53 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/0.3.0...1.0.0) 54 | 55 | ## [0.3.0](https://github.com/alibaba/HandyJSON/tree/0.3.0) (2016-10-01) 56 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/0.2.0...0.3.0) 57 | 58 | **Closed issues:** 59 | 60 | - good job! [\#6](https://github.com/alibaba/HandyJSON/issues/6) 61 | 62 | **Merged pull requests:** 63 | 64 | - Travis-ci support swift3.0 [\#9](https://github.com/alibaba/HandyJSON/pull/9) ([cijianzy](https://github.com/cijianzy)) 65 | - migrate to swift 3.0 [\#8](https://github.com/alibaba/HandyJSON/pull/8) ([xuyecan](https://github.com/xuyecan)) 66 | - reorganize source files and optimize naming [\#7](https://github.com/alibaba/HandyJSON/pull/7) ([xuyecan](https://github.com/xuyecan)) 67 | 68 | ## [0.2.0](https://github.com/alibaba/HandyJSON/tree/0.2.0) (2016-09-27) 69 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/0.1.0...0.2.0) 70 | 71 | **Closed issues:** 72 | 73 | - struct can't have designated initializer. [\#3](https://github.com/alibaba/HandyJSON/issues/3) 74 | 75 | **Merged pull requests:** 76 | 77 | - Dev xyc [\#5](https://github.com/alibaba/HandyJSON/pull/5) ([xuyecan](https://github.com/xuyecan)) 78 | - Add codecov badge to master. [\#4](https://github.com/alibaba/HandyJSON/pull/4) ([cijianzy](https://github.com/cijianzy)) 79 | - Add badge [\#2](https://github.com/alibaba/HandyJSON/pull/2) ([cijianzy](https://github.com/cijianzy)) 80 | - Add support for serializtion [\#1](https://github.com/alibaba/HandyJSON/pull/1) ([cijianzy](https://github.com/cijianzy)) 81 | 82 | ## [0.1.0](https://github.com/alibaba/HandyJSON/tree/0.1.0) (2016-09-21) 83 | 84 | 85 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /HandyJSONTests/SerializeToSimpleObjectTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // 18 | // SerializeToSimpleObjectTest.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 10/23/16. 22 | // 23 | 24 | import Foundation 25 | import XCTest 26 | import HandyJSON 27 | 28 | class serializeToSimpleObjectTest: XCTestCase { 29 | 30 | override func setUp() { 31 | super.setUp() 32 | // Put setup code here. This method is called before the invocation of each test method in the class. 33 | } 34 | 35 | override func tearDown() { 36 | // Put teardown code here. This method is called after the invocation of each test method in the class. 37 | super.tearDown() 38 | } 39 | 40 | func testTransformClassToDictionary() { 41 | class A { 42 | var a: String? 43 | var b: Int? 44 | var c: [Double]? 45 | 46 | init() { 47 | self.a = "hello" 48 | self.b = 12345 49 | self.c = [1.1, 2.1] 50 | } 51 | } 52 | 53 | class B { 54 | var a: A? 55 | var b: [A]? 56 | 57 | init() { 58 | self.a = A() 59 | self.b = [A(), A(), A()] 60 | } 61 | } 62 | 63 | let dict1 = JSONSerializer.serialize(model: A()).toSimpleDictionary()! 64 | XCTAssertEqual(dict1["a"] as! String, "hello") 65 | XCTAssertEqual(dict1["b"] as! Int, 12345) 66 | XCTAssertEqual(dict1["c"] as! [Double], [1.1, 2.1]) 67 | 68 | let dict2 = JSONSerializer.serialize(model: B()).toSimpleDictionary()! 69 | XCTAssertEqual((dict2["a"] as! [String:Any])["a"] as! String, "hello") 70 | 71 | let dict3 = (dict2["b"] as! [Any])[0] as! [String: Any] 72 | XCTAssertEqual(dict3["a"] as! String, "hello") 73 | XCTAssertEqual(dict3["b"] as! Int, 12345) 74 | XCTAssertEqual(dict3["c"] as! [Double], [1.1, 2.1]) 75 | } 76 | 77 | func testTransformStructToDictionary() { 78 | struct A { 79 | var a: String? 80 | var b: Int? 81 | var c: [Double]? 82 | 83 | init() { 84 | self.a = "hello" 85 | self.b = 12345 86 | self.c = [1.1, 2.1] 87 | } 88 | } 89 | 90 | struct B { 91 | var a: A? 92 | var b: [A]? 93 | 94 | init() { 95 | self.a = A() 96 | self.b = [A(), A(), A()] 97 | } 98 | } 99 | 100 | let dict1 = JSONSerializer.serialize(model: A()).toSimpleDictionary()! 101 | XCTAssertEqual(dict1["a"] as! String, "hello") 102 | XCTAssertEqual(dict1["b"] as! Int, 12345) 103 | XCTAssertEqual(dict1["c"] as! [Double], [1.1, 2.1]) 104 | 105 | let dict2 = JSONSerializer.serialize(model: B()).toSimpleDictionary()! 106 | XCTAssertEqual((dict2["a"] as! [String:Any])["a"] as! String, "hello") 107 | 108 | let dict3 = (dict2["b"] as! [Any])[0] as! [String: Any] 109 | XCTAssertEqual(dict3["a"] as! String, "hello") 110 | XCTAssertEqual(dict3["b"] as! Int, 12345) 111 | XCTAssertEqual(dict3["c"] as! [Double], [1.1, 2.1]) 112 | } 113 | 114 | func testTransformComplexDictionaryToSimple() { 115 | class A { 116 | var a: String? 117 | var b: Int? 118 | var c: [Double]? 119 | 120 | init() { 121 | self.a = "hello" 122 | self.b = 12345 123 | self.c = [1.1, 2.1] 124 | } 125 | } 126 | 127 | let dict: [String: Any] = ["a": A(), "b": [A(), A(), A()]] 128 | let simpleDict = JSONSerializer.serialize(dict: dict).toSimpleDictionary()! 129 | let dict3 = (simpleDict["b"] as! [Any])[0] as! [String: Any] 130 | XCTAssertEqual(dict3["a"] as! String, "hello") 131 | XCTAssertEqual(dict3["b"] as! Int, 12345) 132 | XCTAssertEqual(dict3["c"] as! [Double], [1.1, 2.1]) 133 | 134 | let nsDict: NSDictionary = ["a": A(), "b": [A(), A(), A()]] 135 | let simpleDict2 = JSONSerializer.serialize(dict: nsDict).toSimpleDictionary()! 136 | let dict4 = (simpleDict2["b"] as! [Any])[0] as! [String: Any] 137 | XCTAssertEqual(dict4["a"] as! String, "hello") 138 | XCTAssertEqual(dict4["b"] as! Int, 12345) 139 | XCTAssertEqual(dict4["c"] as! [Double], [1.1, 2.1]) 140 | } 141 | 142 | func testTransformComplexArraryToSimple() { 143 | class A { 144 | var a: String? 145 | var b: Int? 146 | var c: [Double]? 147 | 148 | init() { 149 | self.a = "hello" 150 | self.b = 12345 151 | self.c = [1.1, 2.1] 152 | } 153 | } 154 | 155 | let array = [A(), A(), A()] 156 | let simpleArray = JSONSerializer.serialize(array: array as [Any]).toSimpleArray()! 157 | let dict3 = simpleArray[0] as! [String: Any] 158 | XCTAssertEqual(dict3["a"] as! String, "hello") 159 | XCTAssertEqual(dict3["b"] as! Int, 12345) 160 | XCTAssertEqual(dict3["c"] as! [Double], [1.1, 2.1]) 161 | 162 | let nsArray: NSArray = [A(), A(), A()] 163 | let simpleArray2 = JSONSerializer.serialize(array: nsArray).toSimpleArray()! 164 | let dict4 = simpleArray2[0] as! [String: Any] 165 | XCTAssertEqual(dict4["a"] as! String, "hello") 166 | XCTAssertEqual(dict4["b"] as! Int, 12345) 167 | XCTAssertEqual(dict4["c"] as! [Double], [1.1, 2.1]) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /HandyJSON/Converter.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 8/7/16. 18 | // 19 | 20 | import Foundation 21 | 22 | extension NSObject { 23 | 24 | func toInt8() -> Int8? { 25 | if self is NSString { 26 | return Int8((self as! NSString) as String) 27 | } 28 | if self is NSNumber { 29 | return (self as! NSNumber).int8Value 30 | } 31 | return nil 32 | } 33 | 34 | func toUInt8() -> UInt8? { 35 | if self is NSString { 36 | return UInt8((self as! NSString) as String) 37 | } 38 | if self is NSNumber { 39 | return (self as! NSNumber).uint8Value 40 | } 41 | return nil 42 | } 43 | 44 | func toInt16() -> Int16? { 45 | if self is NSString { 46 | return Int16((self as! NSString) as String) 47 | } 48 | if self is NSNumber { 49 | return (self as! NSNumber).int16Value 50 | } 51 | return nil 52 | } 53 | 54 | func toUInt16() -> UInt16? { 55 | if self is NSString { 56 | return UInt16((self as! NSString) as String) 57 | } 58 | if self is NSNumber { 59 | return (self as! NSNumber).uint16Value 60 | } 61 | return nil 62 | } 63 | 64 | func toInt32() -> Int32? { 65 | if self is NSString { 66 | return Int32((self as! NSString) as String) 67 | } 68 | if self is NSNumber { 69 | return (self as! NSNumber).int32Value 70 | } 71 | return nil 72 | } 73 | 74 | func toUInt32() -> UInt32? { 75 | if self is NSString { 76 | return UInt32((self as! NSString) as String) 77 | } 78 | if self is NSNumber { 79 | return (self as! NSNumber).uint32Value 80 | } 81 | return nil 82 | } 83 | 84 | func toInt64() -> Int64? { 85 | if self is NSString { 86 | return Int64((self as! NSString) as String) 87 | } 88 | if self is NSNumber { 89 | return (self as! NSNumber).int64Value 90 | } 91 | return nil 92 | } 93 | 94 | func toUInt64() -> UInt64? { 95 | if self is NSString { 96 | return UInt64((self as! NSString) as String) 97 | } 98 | if self is NSNumber { 99 | return (self as! NSNumber).uint64Value 100 | } 101 | return nil 102 | } 103 | 104 | func toBool() -> Bool? { 105 | if self is NSString { 106 | let lowerCase = ((self as! NSString) as String).lowercased() 107 | if ["0", "false"].contains(lowerCase) { 108 | return false 109 | } 110 | if ["1", "true"].contains(lowerCase) { 111 | return true 112 | } 113 | } 114 | if self is NSNumber { 115 | return (self as! NSNumber).boolValue 116 | } 117 | return nil 118 | } 119 | 120 | func toInt() -> Int? { 121 | if self is NSString { 122 | return Int((self as! NSString) as String) 123 | } 124 | if self is NSNumber { 125 | return (self as! NSNumber).intValue 126 | } 127 | return nil 128 | } 129 | 130 | func toUInt() -> UInt? { 131 | if self is NSString { 132 | return UInt((self as! NSString) as String) 133 | } 134 | if self is NSNumber { 135 | return (self as! NSNumber).uintValue 136 | } 137 | return nil 138 | } 139 | 140 | func toFloat() -> Float? { 141 | if self is NSString { 142 | return Float((self as! NSString) as String) 143 | } 144 | if self is NSNumber { 145 | return (self as! NSNumber).floatValue 146 | } 147 | return nil 148 | } 149 | 150 | func toDouble() -> Double? { 151 | if self is NSString { 152 | return Double((self as! NSString) as String) 153 | } 154 | if self is NSNumber { 155 | return (self as! NSNumber).doubleValue 156 | } 157 | return nil 158 | } 159 | 160 | func toString() -> String? { 161 | if self is NSString { 162 | return self as? String 163 | } else if self is NSNumber { 164 | // Boolean Type Inside 165 | if NSStringFromClass(type(of: self)) == "__NSCFBoolean" { 166 | if (self as! NSNumber).boolValue { 167 | return "true" 168 | } else { 169 | return "false" 170 | } 171 | } 172 | return (self as! NSNumber).stringValue 173 | } else if self is NSArray { 174 | return "\(self as! NSArray)" 175 | } else if self is NSDictionary { 176 | return "\(self as! NSDictionary)" 177 | } 178 | return nil 179 | } 180 | 181 | func toNSString() -> NSString? { 182 | if let s = self.toString() { 183 | return s as NSString 184 | } 185 | return nil 186 | } 187 | 188 | func toNSNumber() -> NSNumber? { 189 | if self is NSNumber { 190 | return self as? NSNumber 191 | } 192 | if self is NSString { 193 | // true/false 194 | if (self as! NSString).lowercased == "true" { 195 | return NSNumber(booleanLiteral: true) 196 | } 197 | if (self as! NSString).lowercased == "false" { 198 | return NSNumber(booleanLiteral: false) 199 | } 200 | 201 | // normal number 202 | let formatter = NumberFormatter() 203 | formatter.numberStyle = .decimal 204 | return formatter.number(from: (self as? NSString)! as String) 205 | } 206 | return nil 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /HandyJSONTests/StructObjectTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 8/7/16. 18 | // 19 | 20 | import XCTest 21 | import HandyJSON 22 | 23 | class StructObjectTest: XCTestCase { 24 | 25 | override func setUp() { 26 | super.setUp() 27 | // Put setup code here. This method is called before the invocation of each test method in the class. 28 | } 29 | 30 | override func tearDown() { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | super.tearDown() 33 | } 34 | 35 | func testSimpleStruct() { 36 | struct A: HandyJSON { 37 | var name: String? 38 | var id: String? 39 | var height: Int? 40 | } 41 | 42 | let jsonString = "{\"name\":\"Bob\",\"id\":\"12345\",\"height\":180}" 43 | let a = JSONDeserializer.deserializeFrom(json: jsonString)! 44 | XCTAssert(a.name == "Bob") 45 | XCTAssert(a.id == "12345") 46 | XCTAssert(a.height == 180) 47 | } 48 | 49 | func testStructWithArrayProperty() { 50 | struct B: HandyJSON { 51 | var id: Int? 52 | var arr1: [Int]? 53 | var arr2: [String]? 54 | } 55 | 56 | let jsonString = "{\"id\":123456,\"arr1\":[1,2,3,4,5,6],\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 57 | let b = JSONDeserializer.deserializeFrom(json: jsonString)! 58 | XCTAssert(b.id == 123456) 59 | XCTAssert(b.arr1?.count == 6) 60 | XCTAssert(b.arr2?.count == 5) 61 | XCTAssert(b.arr1?.last == 6) 62 | XCTAssert(b.arr2?.last == "e") 63 | } 64 | 65 | func testStructWithiImpliicitlyUnwrappedOptionalProperty() { 66 | struct C: HandyJSON { 67 | var id: Int? 68 | var arr1: [Int?]! 69 | var arr2: [String?]? 70 | } 71 | 72 | let jsonString = "{\"id\":123456,\"arr1\":[1,2,3,4,5,6],\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 73 | let c = JSONDeserializer.deserializeFrom(json: jsonString)! 74 | XCTAssert(c.id == 123456) 75 | XCTAssert(c.arr1.count == 6) 76 | XCTAssert(c.arr2?.count == 5) 77 | XCTAssert((c.arr1.last ?? 0) == 6) 78 | XCTAssert((c.arr2?.last ?? "") == "e") 79 | } 80 | 81 | func testStructWithDummyProperty() { 82 | struct C: HandyJSON { 83 | var id: Int? 84 | var arr1: [Int?]! 85 | var arr2: [String?]? 86 | } 87 | struct D: HandyJSON { 88 | var dummy1: String? 89 | var id: Int! 90 | var arr1: [Int]? 91 | var dummy2: C? 92 | var arr2: [String] = [String]() 93 | var dumimy3: C! 94 | } 95 | 96 | let jsonString = "{\"id\":123456,\"arr1\":[1,2,3,4,5,6],\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 97 | let d = JSONDeserializer.deserializeFrom(json: jsonString)! 98 | XCTAssert(d.id == 123456) 99 | XCTAssert(d.arr1?.count == 6) 100 | XCTAssert(d.arr2.count == 5) 101 | XCTAssert(d.arr1?.last == 6) 102 | XCTAssert(d.arr2.last == "e") 103 | } 104 | 105 | func testStructWithiDummyiJsonField() { 106 | struct E: HandyJSON { 107 | var id: Int? 108 | var arr1: [Int?]! 109 | var arr2: [String?]? 110 | } 111 | 112 | let jsonString = "{\"id\":123456,\"dummy1\":23334,\"arr1\":[1,2,3,4,5,6],\"dummy2\":\"string\",\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 113 | let e = JSONDeserializer.deserializeFrom(json: jsonString)! 114 | XCTAssert(e.id == 123456) 115 | XCTAssert(e.arr1.count == 6) 116 | XCTAssert(e.arr2?.count == 5) 117 | XCTAssert((e.arr1.last ?? 0) == 6) 118 | XCTAssert((e.arr2?.last ?? "") == "e") 119 | } 120 | 121 | func testStructWithiDesiginatePath() { 122 | struct F: HandyJSON { 123 | var id: Int? 124 | var arr1: [Int?]! 125 | var arr2: [String?]? 126 | } 127 | 128 | let jsonString = "{\"data\":{\"result\":{\"id\":123456,\"arr1\":[1,2,3,4,5,6],\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}},\"code\":200}" 129 | var f = JSONDeserializer.deserializeFrom(json: jsonString, designatedPath: "data.result")! 130 | XCTAssert(f.id == 123456) 131 | XCTAssert(f.arr1.count == 6) 132 | XCTAssert(f.arr2?.count == 5) 133 | XCTAssert((f.arr1.last ?? 0) == 6) 134 | XCTAssert((f.arr2?.last ?? "") == "e") 135 | 136 | f = JSONDeserializer.deserializeFrom(json: jsonString, designatedPath: "data.result.")! 137 | XCTAssert(f.id == 123456) 138 | XCTAssert(f.arr1.count == 6) 139 | XCTAssert(f.arr2?.count == 5) 140 | XCTAssert((f.arr1.last ?? 0) == 6) 141 | XCTAssert((f.arr2?.last ?? "") == "e") 142 | 143 | f = JSONDeserializer.deserializeFrom(json: jsonString, designatedPath: ".data.result.")! 144 | XCTAssert(f.id == 123456) 145 | XCTAssert(f.arr1.count == 6) 146 | XCTAssert(f.arr2?.count == 5) 147 | XCTAssert((f.arr1.last ?? 0) == 6) 148 | XCTAssert((f.arr2?.last ?? "") == "e") 149 | 150 | let targetJsonString = "{\"id\":123456,\"arr1\":[1,2,3,4,5,6],\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 151 | f = JSONDeserializer.deserializeFrom(json: targetJsonString, designatedPath: "")! 152 | XCTAssert(f.id == 123456) 153 | XCTAssert(f.arr1.count == 6) 154 | XCTAssert(f.arr2?.count == 5) 155 | XCTAssert((f.arr1.last ?? 0) == 6) 156 | XCTAssert((f.arr2?.last ?? "") == "e") 157 | 158 | f = JSONDeserializer.deserializeFrom(json: targetJsonString, designatedPath: ".")! 159 | XCTAssert(f.id == 123456) 160 | XCTAssert(f.arr1.count == 6) 161 | XCTAssert(f.arr2?.count == 5) 162 | XCTAssert((f.arr1.last ?? 0) == 6) 163 | XCTAssert((f.arr2?.last ?? "") == "e") 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /HandyJSON/JSONSerializer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // 18 | // JSONSerializer.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 9/30/16. 22 | // 23 | 24 | public protocol ModelTransformerProtocol { 25 | 26 | func toJSON() -> String? 27 | 28 | func toPrettifyJSON() -> String? 29 | 30 | func toSimpleDictionary() -> [String: Any]? 31 | } 32 | 33 | public protocol ArrayTransformerProtocol { 34 | 35 | func toJSON() -> String? 36 | 37 | func toPrettifyJSON() -> String? 38 | 39 | func toSimpleArray() -> [Any]? 40 | } 41 | 42 | public protocol DictionaryTransformerProtocol { 43 | 44 | func toJSON() -> String? 45 | 46 | func toPrettifyJSON() -> String? 47 | 48 | func toSimpleDictionary() -> [String: Any]? 49 | } 50 | 51 | class GenericObjectTransformer: ModelTransformerProtocol, ArrayTransformerProtocol, DictionaryTransformerProtocol { 52 | 53 | private var object: Any? 54 | 55 | init(of object: Any?) { 56 | self.object = object 57 | } 58 | 59 | public func toSimpleArray() -> [Any]? { 60 | if let _object = self.object, let result = GenericObjectTransformer.transformToSimpleObject(object: _object) { 61 | return result as? [Any] 62 | } 63 | return nil 64 | } 65 | 66 | public func toSimpleDictionary() -> [String: Any]? { 67 | if let _object = self.object, let result = GenericObjectTransformer.transformToSimpleObject(object: _object) { 68 | return result as? [String: Any] 69 | } 70 | return nil 71 | } 72 | 73 | public func toJSON() -> String? { 74 | if let _object = self.object, let result = GenericObjectTransformer.transformToSimpleObject(object: _object) { 75 | return GenericObjectTransformer.transformSimpleObjectToJSON(object: result) 76 | } 77 | return nil 78 | } 79 | 80 | public func toPrettifyJSON() -> String? { 81 | if let result = toJSON() { 82 | let jsonData = result.data(using: String.Encoding.utf8)! 83 | if let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: [.allowFragments]) as AnyObject, let prettyJsonData = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) { 84 | return NSString(data: prettyJsonData, encoding: String.Encoding.utf8.rawValue)! as String 85 | } 86 | } 87 | return nil 88 | } 89 | } 90 | 91 | extension GenericObjectTransformer { 92 | 93 | static func transformToSimpleObject(object: Any) -> Any? { 94 | if (type(of: object) is BasePropertyProtocol.Type) { 95 | return object 96 | } 97 | 98 | let mirror = Mirror(reflecting: object) 99 | 100 | guard let displayStyle = mirror.displayStyle else { 101 | return object 102 | } 103 | 104 | switch displayStyle { 105 | case .class, .struct: 106 | var children = [(label: String?, value: Any)]() 107 | let mirrorChildrenCollection = AnyRandomAccessCollection(mirror.children)! 108 | children += mirrorChildrenCollection 109 | 110 | var currentMirror = mirror 111 | while let superclassChildren = currentMirror.superclassMirror?.children { 112 | let randomCollection = AnyRandomAccessCollection(superclassChildren)! 113 | children += randomCollection 114 | currentMirror = currentMirror.superclassMirror! 115 | } 116 | 117 | var dict = [String: Any]() 118 | children.enumerated().forEach({ (index, element) in 119 | let key = element.label ?? "" 120 | let handledValue = transformToSimpleObject(object: element.value) 121 | if key != "" && handledValue != nil { 122 | dict[key] = handledValue 123 | } 124 | }) 125 | 126 | return dict as Any 127 | case .enum: 128 | return object as Any 129 | case .optional: 130 | if mirror.children.count != 0 { 131 | let (_, some) = mirror.children.first! 132 | return transformToSimpleObject(object: some) 133 | } else { 134 | return nil 135 | } 136 | case .collection, .set: 137 | var array = [Any]() 138 | mirror.children.enumerated().forEach({ (index, element) in 139 | if let transformValue = transformToSimpleObject(object: element.value) { 140 | array.append(transformValue) 141 | } 142 | }) 143 | return array as Any 144 | case .dictionary: 145 | var dict = [String: Any]() 146 | mirror.children.enumerated().forEach({ (index, element) in 147 | let _mirror = Mirror(reflecting: element.value) 148 | var key: String? 149 | var value: Any? 150 | _mirror.children.enumerated().forEach({ (_index, _element) in 151 | if _index == 0 { 152 | key = "\(_element.value)" 153 | } else { 154 | value = transformToSimpleObject(object: _element.value) 155 | } 156 | }) 157 | if (key ?? "") != "" && value != nil { 158 | dict[key!] = value! 159 | } 160 | }) 161 | return dict as Any 162 | default: 163 | return object 164 | } 165 | } 166 | 167 | static func transformSimpleObjectToJSON(object: Any) -> String { 168 | let objectType: Any.Type = type(of: object) 169 | 170 | switch objectType { 171 | case is String.Type, is NSString.Type: 172 | let json = "\"" + String(describing: object) + "\"" 173 | return json 174 | case is BasePropertyProtocol.Type: 175 | let json = String(describing: object) 176 | return json 177 | case is ArrayTypeProtocol.Type: 178 | let array = object as! [Any] 179 | var json = "" 180 | array.enumerated().forEach({ (index, element) in 181 | if index != 0 { 182 | json += "," 183 | } 184 | json += transformSimpleObjectToJSON(object: element) 185 | }) 186 | return "[" + json + "]" 187 | case is DictionaryTypeProtocol.Type: 188 | let dict = object as! [String: Any] 189 | var json = "" 190 | dict.enumerated().forEach({ (index, kv) in 191 | if index != 0 { 192 | json += "," 193 | } 194 | json += "\"\(kv.key)\":\(transformSimpleObjectToJSON(object: kv.value))" 195 | }) 196 | return "{" + json + "}" 197 | default: 198 | return "\"\(String(describing: object))\"" 199 | } 200 | } 201 | } 202 | 203 | public class JSONSerializer { 204 | 205 | public static func serialize(model: Any?) -> ModelTransformerProtocol { 206 | return GenericObjectTransformer(of: model) 207 | } 208 | 209 | public static func serialize(model: AnyObject?) -> ModelTransformerProtocol { 210 | return GenericObjectTransformer(of: model) 211 | } 212 | 213 | public static func serialize(array: [Any]?) -> ArrayTransformerProtocol { 214 | return GenericObjectTransformer(of: array) 215 | } 216 | 217 | public static func serialize(array: [AnyObject]?) -> ArrayTransformerProtocol { 218 | return GenericObjectTransformer(of: array) 219 | } 220 | 221 | public static func serialize(array: NSArray?) -> ArrayTransformerProtocol { 222 | return GenericObjectTransformer(of: array) 223 | } 224 | 225 | public static func serialize(dict: [String: Any]?) -> DictionaryTransformerProtocol { 226 | return GenericObjectTransformer(of: dict) 227 | } 228 | 229 | public static func serialize(dict: [String: AnyObject]?) -> DictionaryTransformerProtocol { 230 | return GenericObjectTransformer(of: dict) 231 | } 232 | 233 | public static func serialize(dict: NSDictionary?) -> DictionaryTransformerProtocol { 234 | return GenericObjectTransformer(of: dict) 235 | } 236 | 237 | public static func serializeToJSON(object: Any?, prettify: Bool = false) -> String? { 238 | if prettify { 239 | return JSONSerializer.serialize(model: object).toPrettifyJSON() 240 | } 241 | return JSONSerializer.serialize(model: object).toJSON() 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /HandyJSONTests/AllBaseTypePropertyObjectTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 8/9/16. 18 | // 19 | 20 | import XCTest 21 | import HandyJSON 22 | 23 | class AllBaseTypePropertyObjectTest: XCTestCase { 24 | 25 | override func setUp() { 26 | super.setUp() 27 | // Put setup code here. This method is called before the invocation of each test method in the class. 28 | } 29 | 30 | override func tearDown() { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | super.tearDown() 33 | } 34 | 35 | func testOptionalStruct() { 36 | /** 37 | { 38 | "aInt": -12345678, 39 | "aInt8": -8, 40 | "aInt16": -16, 41 | "aInt32": -32, 42 | "aInt64": -64, 43 | "aUInt": 12345678, 44 | "aUInt8": 8, 45 | "aUInt16": 16, 46 | "aUInt32": 32, 47 | "aUInt64": 64, 48 | "aBool": true, 49 | "aFloat": 12.34, 50 | "aDouble": 12.34, 51 | "aString": "hello wolrd!" 52 | } 53 | 54 | **/ 55 | struct AStruct : HandyJSON { 56 | var aInt: Int? 57 | var aInt8: Int8? 58 | var aInt16: Int16? 59 | var aInt32: Int32? 60 | var aInt64: Int64? 61 | var aUInt: UInt? 62 | var aUInt8: UInt8? 63 | var aUInt16: UInt16? 64 | var aUInt32: UInt32? 65 | var aUInt64: UInt64? 66 | var aBool: Bool? 67 | var aFloat: Float? 68 | var aDouble: Double? 69 | var aString: String? 70 | } 71 | 72 | let jsonString = "{\"aInt\":-12345678,\"aInt8\":-8,\"aInt16\":-16,\"aInt32\":-32,\"aInt64\":-64,\"aUInt\":12345678,\"aUInt8\":8,\"aUInt16\":16,\"aUInt32\":32,\"aUInt64\":64,\"aBool\":true,\"aFloat\":12.34,\"aDouble\":12.34,\"aString\":\"hello world!\"}" 73 | 74 | let aStruct = JSONDeserializer.deserializeFrom(json: jsonString)! 75 | XCTAssert(aStruct.aInt == -12345678) 76 | XCTAssert(aStruct.aInt8 == -8) 77 | XCTAssert(aStruct.aInt16 == -16) 78 | XCTAssert(aStruct.aInt32 == -32) 79 | XCTAssert(aStruct.aInt64 == -64) 80 | XCTAssert(aStruct.aUInt == 12345678) 81 | XCTAssert(aStruct.aUInt8 == 8) 82 | XCTAssert(aStruct.aUInt16 == 16) 83 | XCTAssert(aStruct.aUInt32 == 32) 84 | XCTAssert(aStruct.aUInt64 == 64) 85 | XCTAssert(aStruct.aBool == true) 86 | XCTAssert(aStruct.aFloat == 12.34) 87 | XCTAssert(aStruct.aDouble == 12.34) 88 | XCTAssert(aStruct.aString == "hello world!") 89 | } 90 | 91 | func testOptionalClass() { 92 | /** 93 | { 94 | "aInt": -12345678, 95 | "aInt8": -8, 96 | "aInt16": -16, 97 | "aInt32": -32, 98 | "aInt64": -64, 99 | "aUInt": 12345678, 100 | "aUInt8": 8, 101 | "aUInt16": 16, 102 | "aUInt32": 32, 103 | "aUInt64": 64, 104 | "aBool": true, 105 | "aFloat": 12.34, 106 | "aDouble": 12.34, 107 | "aString": "hello wolrd!" 108 | } 109 | 110 | **/ 111 | class AClass : HandyJSON { 112 | var aInt: Int? 113 | var aInt8: Int8? 114 | var aInt16: Int16? 115 | var aInt32: Int32? 116 | var aInt64: Int64? 117 | var aUInt: UInt? 118 | var aUInt8: UInt8? 119 | var aUInt16: UInt16? 120 | var aUInt32: UInt32? 121 | var aUInt64: UInt64? 122 | var aBool: Bool? 123 | var aFloat: Float? 124 | var aDouble: Double? 125 | var aString: String? 126 | 127 | required init() {} 128 | } 129 | 130 | let jsonString = "{\"aInt\":-12345678,\"aInt8\":-8,\"aInt16\":-16,\"aInt32\":-32,\"aInt64\":-64,\"aUInt\":12345678,\"aUInt8\":8,\"aUInt16\":16,\"aUInt32\":32,\"aUInt64\":64,\"aBool\":true,\"aFloat\":12.34,\"aDouble\":12.34,\"aString\":\"hello world!\"}" 131 | 132 | let aClass = JSONDeserializer.deserializeFrom(json: jsonString)! 133 | XCTAssert(aClass.aInt == -12345678) 134 | XCTAssert(aClass.aInt8 == -8) 135 | XCTAssert(aClass.aInt16 == -16) 136 | XCTAssert(aClass.aInt32 == -32) 137 | XCTAssert(aClass.aInt64 == -64) 138 | XCTAssert(aClass.aUInt == 12345678) 139 | XCTAssert(aClass.aUInt8 == 8) 140 | XCTAssert(aClass.aUInt16 == 16) 141 | XCTAssert(aClass.aUInt32 == 32) 142 | XCTAssert(aClass.aUInt64 == 64) 143 | XCTAssert(aClass.aBool == true) 144 | XCTAssert(aClass.aFloat == 12.34) 145 | XCTAssert(aClass.aDouble == 12.34) 146 | XCTAssert(aClass.aString == "hello world!") 147 | } 148 | 149 | func testClassImplicitlyUnwrapped() { 150 | /** 151 | { 152 | "aInt": -12345678, 153 | "aInt8": -8, 154 | "aInt16": -16, 155 | "aInt32": -32, 156 | "aInt64": -64, 157 | "aUInt": 12345678, 158 | "aUInt8": 8, 159 | "aUInt16": 16, 160 | "aUInt32": 32, 161 | "aUInt64": 64, 162 | "aBool": true, 163 | "aFloat": 12.34, 164 | "aDouble": 12.34, 165 | "aString": "hello wolrd!" 166 | } 167 | 168 | **/ 169 | class AClassImplicitlyUnwrapped : HandyJSON { 170 | var aInt: Int! 171 | var aInt8: Int8! 172 | var aInt16: Int16! 173 | var aInt32: Int32! 174 | var aInt64: Int64! 175 | var aUInt: UInt! 176 | var aUInt8: UInt8! 177 | var aUInt16: UInt16! 178 | var aUInt32: UInt32! 179 | var aUInt64: UInt64! 180 | var aBool: Bool! 181 | var aFloat: Float! 182 | var aDouble: Double! 183 | var aString: String! 184 | 185 | required init() {} 186 | } 187 | 188 | let jsonString = "{\"aInt\":-12345678,\"aInt8\":-8,\"aInt16\":-16,\"aInt32\":-32,\"aInt64\":-64,\"aUInt\":12345678,\"aUInt8\":8,\"aUInt16\":16,\"aUInt32\":32,\"aUInt64\":64,\"aBool\":true,\"aFloat\":12.34,\"aDouble\":12.34,\"aString\":\"hello world!\"}" 189 | 190 | let aClass = JSONDeserializer.deserializeFrom(json: jsonString)! 191 | XCTAssert(aClass.aInt == -12345678) 192 | XCTAssert(aClass.aInt8 == -8) 193 | XCTAssert(aClass.aInt16 == -16) 194 | XCTAssert(aClass.aInt32 == -32) 195 | XCTAssert(aClass.aInt64 == -64) 196 | XCTAssert(aClass.aUInt == 12345678) 197 | XCTAssert(aClass.aUInt8 == 8) 198 | XCTAssert(aClass.aUInt16 == 16) 199 | XCTAssert(aClass.aUInt32 == 32) 200 | XCTAssert(aClass.aUInt64 == 64) 201 | XCTAssert(aClass.aBool == true) 202 | XCTAssert(aClass.aFloat == 12.34) 203 | XCTAssert(aClass.aDouble == 12.34) 204 | XCTAssert(aClass.aString == "hello world!") 205 | } 206 | 207 | /** 208 | { 209 | "aEnum1": 1, 210 | "bEnum1": 2, 211 | "cEnum1": 3, 212 | "aEnum2": "a", 213 | "bEnum2": "b", 214 | "cEnum2": "c", 215 | "aEnum3": 1.1, 216 | "bEnum3": 2.2, 217 | "cEnum3": 3.3 218 | "aEnumArr": [1, 2, 3], 219 | "bEnumArr": ["a", "b", "c"], 220 | "cEnumArr": [1.1, 2.2, 3.3] 221 | } 222 | */ 223 | func testEnumTypeOfCommonRawType() { 224 | enum AEnum: Int, HandyJSONEnum { 225 | case A = 1, B = 2, C = 3 226 | 227 | static func makeInitWrapper() -> InitWrapperProtocol? { 228 | return InitWrapper(rawInit: AEnum.init) 229 | } 230 | } 231 | 232 | enum BEnum: String, HandyJSONEnum { 233 | case A = "a", B = "b", C = "c" 234 | 235 | static func makeInitWrapper() -> InitWrapperProtocol? { 236 | return InitWrapper(rawInit: BEnum.init) 237 | } 238 | } 239 | 240 | enum CEnum: Double, HandyJSONEnum { 241 | case A = 1.1, B = 2.2, C = 3.3 242 | 243 | static func makeInitWrapper() -> InitWrapperProtocol? { 244 | return InitWrapper(rawInit: CEnum.init) 245 | } 246 | } 247 | 248 | struct TestEnum: HandyJSON { 249 | var aEnum1: AEnum? 250 | var bEnum1: BEnum? 251 | var cEnum1: CEnum? 252 | 253 | var aEnum2: AEnum = .A 254 | var bEnum2: BEnum = .A 255 | var cEnum2: CEnum = .A 256 | 257 | var aEnum3: AEnum! 258 | var bEnum3: BEnum! 259 | var cEnum3: CEnum! 260 | 261 | var aEnumArr: [AEnum]? 262 | var bEnumArr: [BEnum]! 263 | var cEnumArr: [CEnum] = [] 264 | } 265 | 266 | let jsonString = "{\"aEnum1\":1,\"bEnum1\":2,\"cEnum1\":3,\"aEnum2\":\"a\",\"bEnum2\":\"b\",\"cEnum2\":\"c\",\"aEnum3\":1.1,\"bEnum3\":2.2,\"cEnum3\":3.3,\"aEnumArr\":[1,2,3],\"bEnumArr\":[\"a\",\"b\",\"c\"],\"cEnumArr\":[1.1,2.2,3.3]}" 267 | let model = JSONDeserializer.deserializeFrom(json: jsonString)! 268 | 269 | XCTAssert(model.aEnum1 == .A) 270 | XCTAssert(model.bEnum2 == .B) 271 | XCTAssert(model.cEnum3 == .C) 272 | 273 | XCTAssert(model.aEnumArr?[0] == .A) 274 | XCTAssert(model.bEnumArr[1] == .B) 275 | XCTAssert(model.cEnumArr[2] == .C) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 | # HandyJSON 2 | 3 | HandyJSON是一个用于Swift语言中的JSON序列化/反序列化库。 4 | 5 | 与其他流行的Swift JSON库相比,HandyJSON的特点是,它反序列化时(把JSON转换为Model)不要求Model从`NSObject`继承(因为它不是基于`KVC`机制),也不要求你为Model定义一个`Mapping`函数。只要你定义好Model类,声明它服从`HandyJSON`协议,HandyJSON就能自行以各个属性的属性名为Key,从JSON串中解析值。 6 | 7 | 需要注意,HandyJSON在反序列化时是根据Model的内存布局来为各个属性赋值的,所以,它完全依赖于Swift的内存布局规则。这个库实现里涉及的规则是从一些三方资料中找到说明,加上自己反复验证总结得到。Swift从诞生到现在一直没有改变过这些规则,但毕竟不是苹果官方说明,所以仍然存在一定的风险。如果Swift日后更新改变这些规则,HandyJSON会第一时间跟进做好兼容工作。 8 | 9 | [![Build Status](https://travis-ci.org/alibaba/HandyJSON.svg?branch=master)](https://travis-ci.org/alibaba/HandyJSON) 10 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 11 | [![Cocoapods Version](https://img.shields.io/cocoapods/v/HandyJSON.svg?style=flat)](http://cocoadocs.org/docsets/HandyJSON) 12 | [![Cocoapods Platform](https://img.shields.io/cocoapods/p/HandyJSON.svg?style=flat)](http://cocoadocs.org/docsets/HandyJSON) 13 | [![Codecov branch](https://img.shields.io/codecov/c/github/alibaba/HandyJSON/master.svg?style=flat)](https://codecov.io/gh/alibaba/HandyJSON/branch/master) 14 | 15 | ## 简单示例 16 | 17 | ### 反序列化 18 | 19 | ```swift 20 | class Animal: HandyJSON { 21 | var name: String? 22 | var count: Int? 23 | 24 | required init() {} 25 | } 26 | 27 | let json = "{\"name\": \"Cat\", \"count\": 5}" 28 | 29 | if let cat = JSONDeserializer.deserializeFrom(json: json) { 30 | print(cat) 31 | } 32 | ``` 33 | 34 | ### 序列化 35 | 36 | ```swift 37 | class Animal { 38 | var name: String? 39 | var count: Int? 40 | 41 | init(name: String, count: Int) { 42 | self.name = name 43 | self.count = count 44 | } 45 | } 46 | 47 | let cat = Animal(name: "cat", count: 5) 48 | 49 | print(JSONSerializer.serialize(model: cat).toJSON()!) 50 | print(JSONSerializer.serialize(model: cat).toPrettifyJSON()!) 51 | print(JSONSerializer.serialize(model: cat).toSimpleDictionary()!) 52 | ``` 53 | 54 | # 文档目录 55 | 56 | - [特性](#特性) 57 | - [环境要求](#环境要求) 58 | - [安装](#安装) 59 | - [反序列化](#反序列化-1) 60 | - [基本类型](#基本类型) 61 | - [支持struct](#支持struct) 62 | - [支持enum](#支持enum) 63 | - [可选、隐式解包可选、集合等](#可选隐式解包可选集合等) 64 | - [指定解析路径](#指定解析路径) 65 | - [组合对象](#组合对象) 66 | - [继承自父类的子类](#继承自父类的子类) 67 | - [JSON中的数组](#json中的数组) 68 | - [自定义解析规则](#自定义解析规则) 69 | - [支持的属性类型](#支持的属性类型) 70 | - [序列化](#序列化-1) 71 | - [基本类型](#基本类型-1) 72 | - [复杂类型](#复杂类型) 73 | - [待办](#待办) 74 | 75 | # 特性 76 | 77 | * 序列化Model到JSON、从JSON反序列化到Model 78 | 79 | * 自然地以Model的属性名称作为解析JSON的Key,不需要额外指定 80 | 81 | * 支持Swift中大部分类型 82 | 83 | * 支持class、struct定义的Model 84 | 85 | * 支持自定义解析规则 86 | 87 | * 类型自适应,如JSON中是一个Int,但对应Model是String字段,会自动完成转化 88 | 89 | # 环境要求 90 | 91 | * iOS 8.0+/OSX 10.9+/watchOS 2.0+/tvOS 9.0+ 92 | 93 | * Swift 2.3+ / Swift 3.0+ 94 | 95 | # 安装 96 | 97 | HandyJSON只在Swift3.x版本上(master分支)开发新特性,在Swift2.x中使用,参见: [swift2 branch](https://github.com/alibaba/HandyJSON/tree/master_for_swift_2x) 98 | 99 | 具体操作指引参考 [英文版README](./README.md) 的 `Installation` 章节。 100 | 101 | # 反序列化 102 | 103 | ## 基本类型 104 | 105 | 要支持从JSON串反序列化,Model定义时要声明服从`HandyJSON`协议。确实是一个协议,而不是继承自`NSObject`。 106 | 107 | 服从`HandyJSON`协议,需要实现一个空的`init`方法。 108 | 109 | ```swift 110 | class Animal: HandyJSON { 111 | var name: String? 112 | var id: String? 113 | var num: Int? 114 | 115 | required init() {} 116 | } 117 | 118 | let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}" 119 | 120 | if let animal = JSONDeserializer.deserializeFrom(json: jsonString) { 121 | print(animal) 122 | } 123 | ``` 124 | 125 | ## 支持struct 126 | 127 | 对于声明为`struct`的Model,由于`struct`默认提供了空的`init`方法,所以不需要额外声明。 128 | 129 | ```swift 130 | struct Animal: HandyJSON { 131 | var name: String? 132 | var id: String? 133 | var num: Int? 134 | } 135 | 136 | let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}" 137 | 138 | if let animal = JSONDeserializer.deserializeFrom(json: jsonString) { 139 | print(animal) 140 | } 141 | ``` 142 | 143 | 但需要注意,如果你为`struct`指定了别的构造函数,那就要显示声明一个空的`init`函数。 144 | 145 | ## 支持enum 146 | 147 | 由于受到类型转换的一些限制,对`enum`的支持需要一些特殊处理。要支持反序列化的`enum`类型需要服从`HandyJSONEnum`协议,并实现协议要求的`makeInitWrapper`函数。 148 | 149 | ```swift 150 | enum AnimalType: String, HandyJSONEnum { 151 | case Cat = "cat" 152 | case Dog = "dog" 153 | case Bird = "bird" 154 | 155 | static func makeInitWrapper() -> InitWrapperProtocol? { 156 | return InitWrapper(rawInit: AnimalType.init) 157 | } 158 | } 159 | 160 | class Animal: HandyJSON { 161 | var type: AnimalType? 162 | var name: String? 163 | 164 | required init() {} 165 | } 166 | 167 | let jsonString = "{\"type\":\"cat\",\"name\":\"Tom\"}" 168 | if let animal = JSONDeserializer.deserializeFrom(json: jsonString) { 169 | print(animal) 170 | } 171 | ``` 172 | 173 | 在`makeInitWrapper`函数中将`RawRepresentable`的`init`函数包装一下,返回就可以了。如果觉得对代码有侵入,可以考虑用扩展实现。 174 | 175 | ```swift 176 | enum AnimalType: String { 177 | case Cat = "cat" 178 | case Dog = "dog" 179 | case Bird = "bird" 180 | } 181 | 182 | extension AnimalType: HandyJSONEnum { 183 | static func makeInitWrapper() -> InitWrapperProtocol? { 184 | return InitWrapper(rawInit: AnimalType.init) 185 | } 186 | } 187 | 188 | ... 189 | ``` 190 | 191 | 这样对原来的`enum`类型就没有侵入了。 192 | 193 | ## 可选、隐式解包可选、集合等 194 | 195 | HandyJSON支持这些非基础类型,包括嵌套结构。 196 | 197 | ```swift 198 | class Cat: HandyJSON { 199 | var id: Int64! 200 | var name: String! 201 | var friend: [String]? 202 | var weight: Double? 203 | var alive: Bool = true 204 | var color: NSString? 205 | 206 | required init() {} 207 | } 208 | 209 | let jsonString = "{\"id\":1234567,\"name\":\"Kitty\",\"friend\":[\"Tom\",\"Jack\",\"Lily\",\"Black\"],\"weight\":15.34,\"alive\":false,\"color\":\"white\"}" 210 | 211 | if let cat = JSONDeserializer.deserializeFrom(json: jsonString) { 212 | print(cat) 213 | } 214 | ``` 215 | 216 | ## 指定解析路径 217 | 218 | HandyJSON支持指定从哪个具体路径开始解析,反序列化到Model。 219 | 220 | ```swift 221 | class Cat: HandyJSON { 222 | var id: Int64! 223 | var name: String! 224 | 225 | required init() {} 226 | } 227 | 228 | let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}" 229 | 230 | if let cat = JSONDeserializer.deserializeFrom(json: jsonString, designatedPath: "data.cat") { 231 | print(cat.name) 232 | } 233 | ``` 234 | 235 | ## 组合对象 236 | 237 | 注意,如果Model的属性不是基本类型或集合类型,那么它必须是一个服从`HandyJSON`协议的类型。 238 | 239 | 如果是泛型集合类型,那么要求泛型实参是基本类型或者服从`HandyJSON`协议的类型。 240 | 241 | ```swift 242 | class Component: HandyJSON { 243 | var aInt: Int? 244 | var aString: String? 245 | 246 | required init() {} 247 | } 248 | 249 | class Composition: HandyJSON { 250 | var aInt: Int? 251 | var comp1: Component? 252 | var comp2: Component? 253 | 254 | required init() {} 255 | } 256 | 257 | let jsonString = "{\"num\":12345,\"comp1\":{\"aInt\":1,\"aString\":\"aaaaa\"},\"comp2\":{\"aInt\":2,\"aString\":\"bbbbb\"}}" 258 | 259 | if let composition = JSONDeserializer.deserializeFrom(json: jsonString) { 260 | print(composition) 261 | } 262 | ``` 263 | 264 | ## 继承自父类的子类 265 | 266 | 如果子类要支持反序列化,那么要求父类也服从`HandyJSON`协议。 267 | 268 | ```swift 269 | class Animal: HandyJSON { 270 | var id: Int? 271 | var color: String? 272 | 273 | required init() {} 274 | } 275 | 276 | 277 | class Cat: Animal { 278 | var name: String? 279 | 280 | required init() {} 281 | } 282 | 283 | let jsonString = "{\"id\":12345,\"color\":\"black\",\"name\":\"cat\"}" 284 | 285 | if let cat = JSONDeserializer.deserializeFrom(json: jsonString) { 286 | print(cat) 287 | } 288 | ``` 289 | 290 | ## JSON中的数组 291 | 292 | 如果JSON的第一层表达的是数组,可以转化它到一个Model数组。 293 | 294 | ```swift 295 | class Cat: HandyJSON { 296 | var name: String? 297 | var id: String? 298 | 299 | required init() {} 300 | } 301 | 302 | let jsonArrayString: String? = "[{\"name\":\"Bob\",\"id\":\"1\"}, {\"name\":\"Lily\",\"id\":\"2\"}, {\"name\":\"Lucy\",\"id\":\"3\"}]" 303 | if let cats = JSONDeserializer.deserializeModelArrayFrom(json: jsonArrayString) { 304 | cats.forEach({ (cat) in 305 | if let _cat = cat { 306 | print(_cat.id ?? "", _cat.name ?? "") 307 | } 308 | }) 309 | } 310 | ``` 311 | 312 | ## 自定义解析规则 313 | 314 | HandyJSON支持自定义映射关系,或者自定义解析过程。你需要实现一个可选的`mapping`函数,在里边实现`NSString`值(HandyJSON会把对应的JSON字段转换为NSString)转换为你需要的字段类型。 315 | 316 | ```swift 317 | class Cat: HandyJSON { 318 | var id: Int64! 319 | var name: String! 320 | var parent: (String, String)? 321 | 322 | required init() {} 323 | 324 | func mapping(mapper: HelpingMapper) { 325 | // 指定 JSON中的`cat_id`字段映射到Model中的`id`字段 326 | mapper.specify(property: &id, name: "cat_id") 327 | 328 | // 指定JSON中的`parent`字段解析为Model中的`parent`字段 329 | // 因为(String, String)?是一个元组,既不是基本类型,也不服从`HandyJSON`协议,所以需要自己实现解析过程 330 | mapper.specify(property: &parent, converter: { (rawString) -> (String, String) in 331 | let parentNames = rawString.characters.split{$0 == "/"}.map(String.init) 332 | return (parentNames[0], parentNames[1]) 333 | }) 334 | } 335 | } 336 | 337 | let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\"}" 338 | 339 | if let cat = JSONDeserializer.deserializeFrom(json: jsonString) { 340 | print(cat) 341 | } 342 | ``` 343 | 344 | ## 支持的属性类型 345 | 346 | * `Int`/`Bool`/`Double`/`Float`/`String`/`NSNumber`/`NSString` 347 | 348 | * `NSArray/NSDictionary` 349 | 350 | * `Int8/Int16/Int32/Int64`/`UInt8/UInt16/UInt23/UInt64` 351 | 352 | * `Optional/ImplicitUnwrappedOptional` // T is one of the above types 353 | 354 | * `Array` // T is one of the above types 355 | 356 | * `Dictionary` // T is one of the above types 357 | 358 | * 以上类型的嵌套 359 | 360 | # 序列化 361 | 362 | ## 基本类型 363 | 364 | 不需要为序列化做额外的工作。给出Model定义,构造出实例,就可以直接调用HandyJSON把它转化为JSON文本串,或者只有基础类型的简单字典。 365 | 366 | ```swift 367 | class Animal { 368 | var name: String? 369 | var height: Int? 370 | 371 | init(name: String, height: Int) { 372 | self.name = name 373 | self.height = height 374 | } 375 | } 376 | 377 | let cat = Animal(name: "cat", height: 30) 378 | if let jsonStr = JSONSerializer.serialize(model: cat).toJSON() { 379 | print("simple json string: ", jsonStr) 380 | } 381 | if let prettifyJSON = JSONSerializer.serialize(model: cat).toPrettifyJSON() { 382 | print("prettify json string: ", prettifyJSON) 383 | } 384 | if let dict = JSONSerializer.serialize(model: cat).toSimpleDictionary() { 385 | print("dictionary: ", dict) 386 | } 387 | ``` 388 | 389 | ## 复杂类型 390 | 391 | 仍然不需要额外的工作,直接调接口就可以。 392 | 393 | ```swift 394 | enum Gender { 395 | case Male 396 | case Female 397 | } 398 | 399 | struct Subject { 400 | var id: Int64? 401 | var name: String? 402 | 403 | init(id: Int64, name: String) { 404 | self.id = id 405 | self.name = name 406 | } 407 | } 408 | 409 | class Student { 410 | var name: String? 411 | var gender: Gender? 412 | var subjects: [Subject]? 413 | } 414 | 415 | let student = Student() 416 | student.name = "Jack" 417 | student.gender = .Female 418 | student.subjects = [Subject(id: 1, name: "math"), Subject(id: 2, name: "English"), Subject(id: 3, name: "Philosophy")] 419 | 420 | if let jsonStr = JSONSerializer.serialize(model: student).toJSON() { 421 | print("simple json string: ", jsonStr) 422 | } 423 | if let prettifyJSON = JSONSerializer.serialize(model: student).toPrettifyJSON() { 424 | print("prettify json string: ", prettifyJSON) 425 | } 426 | if let dict = JSONSerializer.serialize(model: student).toSimpleDictionary() { 427 | print("dictionary: ", dict) 428 | } 429 | ``` 430 | 431 | # 待办 432 | 433 | * 完善测试 434 | 435 | * 完善异常处理 436 | 437 | # License 438 | 439 | HandyJSON is released under the Apache License, Version 2.0. See LICENSE for details. 440 | -------------------------------------------------------------------------------- /HandyJSONTests/SerializeToJSONTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by cijianzy(cijainzy@gmail.com) on 17/9/16. 18 | // 19 | 20 | import Foundation 21 | import XCTest 22 | import HandyJSON 23 | 24 | class serializeToJSONTests: XCTestCase { 25 | 26 | override func setUp() { 27 | super.setUp() 28 | // Put setup code here. This method is called before the invocation of each test method in the class. 29 | } 30 | 31 | override func tearDown() { 32 | // Put teardown code here. This method is called after the invocation of each test method in the class. 33 | super.tearDown() 34 | } 35 | 36 | func stringCompareHelper(_ actual: String?, _ expected: String?) { 37 | print(actual ?? "") 38 | print(expected ?? "") 39 | XCTAssertTrue(expected == actual, "expected value:\(expected) not equal to actual:\(actual)") 40 | } 41 | 42 | func testForBasicTypeValue() { 43 | stringCompareHelper(JSONSerializer.serializeToJSON(object: 1), "1") 44 | stringCompareHelper(JSONSerializer.serializeToJSON(object: "HandyJSON"), "\"HandyJSON\"") 45 | stringCompareHelper(JSONSerializer.serializeToJSON(object: 1.2), "1.2") 46 | stringCompareHelper(JSONSerializer.serializeToJSON(object: 10), "10") 47 | stringCompareHelper(JSONSerializer.serializeToJSON(object: true), "true") 48 | 49 | enum Week: String { 50 | case Monday 51 | case Tuesday 52 | case Wednesday 53 | case Thursday 54 | case Friday 55 | case Saturday 56 | case Sunday 57 | } 58 | stringCompareHelper(JSONSerializer.serializeToJSON(object: Week.Friday), "\"Friday\"") 59 | } 60 | 61 | func testForIntArray() { 62 | let array = [1,2,3,4] 63 | let json = JSONSerializer.serializeToJSON(object: array) 64 | stringCompareHelper(json, "[1,2,3,4]") 65 | } 66 | 67 | func testForStringArray() { 68 | let array = ["Monday", "Tuesday", "Wednesday"] 69 | let json = JSONSerializer.serializeToJSON(object: array) 70 | stringCompareHelper(json, "[\"Monday\",\"Tuesday\",\"Wednesday\"]") 71 | } 72 | 73 | func testForNSDictionary() { 74 | let dic: NSDictionary = NSDictionary.init(dictionary: ["Today": "Monday"]) 75 | let json = JSONSerializer.serializeToJSON(object: dic) 76 | stringCompareHelper(json, "{\"Today\":\"Monday\"}") 77 | } 78 | 79 | func testForClass() { 80 | enum Week: String { 81 | case Monday 82 | case Tuesday 83 | case Wednesday 84 | case Thursday 85 | case Friday 86 | case Saturday 87 | case Sunday 88 | } 89 | 90 | class ClassA { 91 | let month = 4 92 | let day = 18 93 | let year = 1994 94 | let today = Week.Monday 95 | let name = "cijian" 96 | let time = ["Morning","Afternoon","Night"] 97 | let dic = ["today": "Monday", "tomorrow": "Tuesday"] 98 | } 99 | 100 | stringCompareHelper(JSONSerializer.serializeToJSON(object: ClassA()), "{\"name\":\"cijian\",\"day\":18,\"year\":1994,\"today\":\"Monday\",\"month\":4,\"dic\":{\"tomorrow\":\"Tuesday\",\"today\":\"Monday\"},\"time\":[\"Morning\",\"Afternoon\",\"Night\"]}") 101 | } 102 | 103 | func testForOptionalBasicValueType() { 104 | var optionalInt: Int? 105 | var optionalString: String? 106 | var optionalBool: Bool? 107 | var optionalDouble: Double? 108 | var optionalNil: String? 109 | 110 | optionalInt = 10 111 | optionalString = "hello" 112 | optionalBool = false 113 | optionalDouble = 1.23 114 | optionalNil = nil 115 | 116 | stringCompareHelper(JSONSerializer.serializeToJSON(object: optionalInt),"10") 117 | stringCompareHelper(JSONSerializer.serializeToJSON(object: optionalString),"\"hello\"") 118 | stringCompareHelper(JSONSerializer.serializeToJSON(object: optionalBool),"false") 119 | stringCompareHelper(JSONSerializer.serializeToJSON(object: optionalDouble),"1.23") 120 | XCTAssertNil(JSONSerializer.serializeToJSON(object: optionalNil)) 121 | } 122 | 123 | func testForOptionalInArray() { 124 | var optionalvalue: Int? 125 | var array: [Int?] = [] 126 | optionalvalue = 1 127 | array.append(optionalvalue) 128 | optionalvalue = 10 129 | array.append(optionalvalue) 130 | optionalvalue = 50 131 | array.append(optionalvalue) 132 | stringCompareHelper(JSONSerializer.serializeToJSON(object: array),"[1,10,50]") 133 | } 134 | 135 | func testForClassWithOptinalValue() { 136 | class ClassA { 137 | var value: Int? = 2 138 | } 139 | stringCompareHelper(JSONSerializer.serializeToJSON(object: ClassA()),"{\"value\":2}") 140 | } 141 | 142 | func testForStringBaseEnum() { 143 | enum Week: String { 144 | case Monday 145 | case Tuesday 146 | case Wednesday 147 | case Thursday 148 | case Friday 149 | case Saturday 150 | case Sunday 151 | } 152 | 153 | class ClassA { 154 | 155 | var today = Week.Monday 156 | var tomorrow = Week.Tuesday 157 | } 158 | 159 | let expected = "{\"tomorrow\":\"Tuesday\",\"today\":\"Monday\"}" 160 | 161 | let json = JSONSerializer.serializeToJSON(object: ClassA()) 162 | 163 | stringCompareHelper(json, expected) 164 | } 165 | 166 | func testForIntBaseEnum() { 167 | enum Week: Int { 168 | case Monday = 1000 169 | case Tuesday 170 | case Wednesday 171 | case Thursday 172 | case Friday 173 | case Saturday 174 | case Sunday 175 | } 176 | class ClassA { 177 | 178 | var today = Week.Monday 179 | var tomorrow = Week.Tuesday 180 | } 181 | let expected = "{\"tomorrow\":\"Tuesday\",\"today\":\"Monday\"}" 182 | stringCompareHelper(JSONSerializer.serializeToJSON(object: ClassA()), expected) 183 | } 184 | 185 | func testForStringBaseEnumWithCustomValue() { 186 | enum Week: String { 187 | case Monday = "hello" 188 | case Tuesday 189 | case Wednesday 190 | case Thursday 191 | case Friday 192 | case Saturday 193 | case Sunday 194 | } 195 | class ClassA { 196 | var today = Week.Monday 197 | var tomorrow = Week.Tuesday 198 | } 199 | let expected = "{\"tomorrow\":\"Tuesday\",\"today\":\"Monday\"}" 200 | stringCompareHelper(JSONSerializer.serializeToJSON(object: ClassA()), expected) 201 | } 202 | 203 | func testForClassWithDictionaryProperty() { 204 | class ClassA { 205 | var dic = ["today": "Monday", "tomorrow": "Tuesday"] 206 | } 207 | 208 | let expected = "{\"dic\":{\"tomorrow\":\"Tuesday\",\"today\":\"Monday\"}}" 209 | stringCompareHelper(JSONSerializer.serializeToJSON(object: ClassA()), expected) 210 | } 211 | 212 | func testForCompositionClass() { 213 | class A { 214 | var a: String? 215 | var b: Int? 216 | var c: [Double]? 217 | var d: [String: String]? 218 | 219 | init() { 220 | self.a = "hello" 221 | self.b = 111 222 | self.c = [1, 2.1, 3, 4.0] 223 | self.d = ["name1": "value1", "name2": "value2"] 224 | } 225 | } 226 | 227 | class B { 228 | var a: A? 229 | var b: A? 230 | 231 | init() { 232 | self.a = A() 233 | self.b = A() 234 | } 235 | } 236 | 237 | let expected = "{\"b\":{\"b\":111,\"a\":\"hello\",\"d\":{\"name1\":\"value1\",\"name2\":\"value2\"},\"c\":[1.0,2.1,3.0,4.0]},\"a\":{\"b\":111,\"a\":\"hello\",\"d\":{\"name1\":\"value1\",\"name2\":\"value2\"},\"c\":[1.0,2.1,3.0,4.0]}}" 238 | stringCompareHelper(JSONSerializer.serializeToJSON(object: B()), expected) 239 | print(JSONSerializer.serializeToJSON(object: B(), prettify: true)!) 240 | } 241 | 242 | func testForCompositionStruct() { 243 | struct A { 244 | var a: String? 245 | var b: Int? 246 | var c: [Double]? 247 | var d: [String: String]? 248 | 249 | init() { 250 | self.a = "hello" 251 | self.b = 111 252 | self.c = [1, 2.1, 3, 4.0] 253 | self.d = ["name1": "value1", "name2": "value2"] 254 | } 255 | } 256 | 257 | struct B { 258 | var a: A? 259 | var b: A? 260 | 261 | init() { 262 | self.a = A() 263 | self.b = A() 264 | } 265 | } 266 | 267 | let expected = "{\"b\":{\"b\":111,\"a\":\"hello\",\"d\":{\"name1\":\"value1\",\"name2\":\"value2\"},\"c\":[1.0,2.1,3.0,4.0]},\"a\":{\"b\":111,\"a\":\"hello\",\"d\":{\"name1\":\"value1\",\"name2\":\"value2\"},\"c\":[1.0,2.1,3.0,4.0]}}" 268 | stringCompareHelper(JSONSerializer.serializeToJSON(object: B()), expected) 269 | print(JSONSerializer.serializeToJSON(object: B(), prettify: true)!) 270 | } 271 | 272 | func testForClassWithComplexDictionaryProperty() { 273 | class ClassB { 274 | var value: Int? 275 | 276 | init(value: Int) { 277 | self.value = value 278 | } 279 | } 280 | 281 | class ClassA { 282 | var dic = ["today": ClassB(value: 1), "tomorrow": ClassB(value: 2)] 283 | } 284 | 285 | let expected = "{\"dic\":{\"tomorrow\":{\"value\":2},\"today\":{\"value\":1}}}" 286 | stringCompareHelper(JSONSerializer.serializeToJSON(object: ClassA()), expected) 287 | 288 | } 289 | 290 | func testForClassDefinedInsideBlock() { 291 | _ = { 292 | class ClassA { 293 | var dic = ["today": "Monday", "tomorrow": "Tuesday"] 294 | } 295 | 296 | let expected = "{\"dic\":{\"tomorrow\":\"Tuesday\",\"today\":\"Monday\"}}" 297 | stringCompareHelper(JSONSerializer.serializeToJSON(object: ClassA()), expected) 298 | }() 299 | } 300 | 301 | func testForSet() { 302 | let set: Set = [1, 2, 3, 4] 303 | stringCompareHelper(JSONSerializer.serializeToJSON(object: set), "[2,3,1,4]") 304 | } 305 | 306 | func testForClassWithSetProperty() { 307 | class ClassA { 308 | let set: Set = [1, 2, 3, 4] 309 | } 310 | 311 | stringCompareHelper(JSONSerializer.serializeToJSON(object: ClassA()), "{\"set\":[2,3,1,4]}") 312 | } 313 | 314 | func testForInheritedClass() { 315 | class A { 316 | var a: Int? 317 | var b: String? 318 | 319 | init() { 320 | self.a = 1 321 | self.b = "hello" 322 | } 323 | } 324 | 325 | class B: A { 326 | var c: Double? 327 | var d: [Bool]? 328 | 329 | override init() { 330 | super.init() 331 | self.c = 123.45 332 | self.d = [false, true, false] 333 | } 334 | } 335 | 336 | let expected = "{\"b\":\"hello\",\"a\":1,\"d\":[false,true,false],\"c\":123.45}" 337 | stringCompareHelper(JSONSerializer.serializeToJSON(object: B()), expected) 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /HandyJSON/Property.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 7/7/16. 18 | // 19 | 20 | import Foundation 21 | 22 | typealias Byte = Int8 23 | 24 | public protocol Property { 25 | } 26 | 27 | extension Property { 28 | 29 | // locate the head of a struct type object in memory 30 | mutating func headPointerOfStruct() -> UnsafeMutablePointer { 31 | 32 | return withUnsafeMutablePointer(to: &self) { 33 | return UnsafeMutableRawPointer($0).bindMemory(to: Byte.self, capacity: MemoryLayout.stride) 34 | } 35 | } 36 | 37 | // locating the head of a class type object in memory 38 | mutating func headPointerOfClass() -> UnsafeMutablePointer { 39 | 40 | let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque() 41 | let mutableTypedPointer = opaquePointer.bindMemory(to: Byte.self, capacity: MemoryLayout.stride) 42 | return UnsafeMutablePointer(mutableTypedPointer) 43 | } 44 | 45 | // memory size occupy by self object 46 | static func size() -> Int { 47 | return MemoryLayout.size 48 | } 49 | 50 | // align 51 | static func align() -> Int { 52 | return MemoryLayout.alignment 53 | } 54 | 55 | // Returns the offset to the next integer that is greater than 56 | // or equal to Value and is a multiple of Align. Align must be 57 | // non-zero. 58 | static func offsetToAlignment(value: Int, align: Int) -> Int { 59 | let m = value % align 60 | return m == 0 ? 0 : (align - m) 61 | } 62 | } 63 | 64 | public protocol HandyJSON: Property { 65 | init() 66 | mutating func mapping(mapper: HelpingMapper) 67 | } 68 | 69 | public extension HandyJSON { 70 | public mutating func mapping(mapper: HelpingMapper) {} 71 | } 72 | 73 | public protocol InitWrapperProtocol { 74 | func convertToEnum(object: NSObject) -> Any? 75 | } 76 | 77 | public struct InitWrapper: InitWrapperProtocol { 78 | 79 | var _init: ((T) -> Any?)? 80 | 81 | public init(rawInit: @escaping ((T) -> Any?)) { 82 | self._init = rawInit 83 | } 84 | 85 | public func convertToEnum(object: NSObject) -> Any? { 86 | if let typedValue = T.valueFrom(object: object) { 87 | return _init?(typedValue) 88 | } 89 | return nil 90 | } 91 | } 92 | 93 | public protocol HandyJSONEnum: Property { 94 | static func makeInitWrapper() -> InitWrapperProtocol? 95 | } 96 | 97 | protocol BasePropertyProtocol: HandyJSON { 98 | } 99 | 100 | protocol OptionalTypeProtocol: HandyJSON { 101 | static func optionalFromNSObject(object: NSObject) -> Any? 102 | } 103 | 104 | extension Optional: OptionalTypeProtocol { 105 | public init() { 106 | self = nil 107 | } 108 | 109 | static func optionalFromNSObject(object: NSObject) -> Any? { 110 | if let value = (Wrapped.self as? Property.Type)?.valueFrom(object: object) as? Wrapped { 111 | return Optional(value) 112 | } 113 | return nil 114 | } 115 | } 116 | 117 | protocol ImplicitlyUnwrappedTypeProtocol: HandyJSON { 118 | static func implicitlyUnwrappedOptionalFromNSObject(object: NSObject) -> Any? 119 | } 120 | 121 | extension ImplicitlyUnwrappedOptional: ImplicitlyUnwrappedTypeProtocol { 122 | 123 | static func implicitlyUnwrappedOptionalFromNSObject(object: NSObject) -> Any? { 124 | if let value = (Wrapped.self as? Property.Type)?.valueFrom(object: object) as? Wrapped { 125 | return ImplicitlyUnwrappedOptional(value) 126 | } 127 | return nil 128 | } 129 | } 130 | 131 | protocol ArrayTypeProtocol: HandyJSON { 132 | static func arrayFromNSObject(object: NSObject) -> Any? 133 | } 134 | 135 | extension Array: ArrayTypeProtocol { 136 | 137 | static func arrayFromNSObject(object: NSObject) -> Any? { 138 | guard let nsArray = object as? NSArray else { 139 | return nil 140 | } 141 | var result: [Element] = [Element]() 142 | nsArray.forEach { (each) in 143 | if let nsObject = each as? NSObject, let element = (Element.self as? Property.Type)?.valueFrom(object: nsObject) as? Element { 144 | result.append(element) 145 | } 146 | } 147 | return result 148 | } 149 | } 150 | 151 | protocol DictionaryTypeProtocol: HandyJSON { 152 | static func dictionaryFromNSObject(object: NSObject) -> Any? 153 | } 154 | 155 | extension Dictionary: DictionaryTypeProtocol { 156 | 157 | static func dictionaryFromNSObject(object: NSObject) -> Any? { 158 | guard let nsDict = object as? NSDictionary else { 159 | return nil 160 | } 161 | var result: [Key: Value] = [Key: Value]() 162 | nsDict.forEach { (key, value) in 163 | if let sKey = key as? Key, let nsValue = value as? NSObject, let nValue = (Value.self as? Property.Type)?.valueFrom(object: nsValue) as? Value { 164 | result[sKey] = nValue 165 | } 166 | } 167 | return result 168 | } 169 | } 170 | 171 | extension NSArray: Property {} 172 | extension NSDictionary: Property {} 173 | 174 | extension Property { 175 | 176 | internal static func _transform(rawData dict: NSDictionary, toPointer pointer: UnsafeMutablePointer, toOffset currentOffset: Int, byMirror mirror: Mirror, withMapper mapper: HelpingMapper) -> Int { 177 | 178 | var currentOffset = currentOffset 179 | if let superMirror = mirror.superclassMirror { 180 | currentOffset = _transform(rawData: dict, toPointer: pointer, toOffset: currentOffset, byMirror: superMirror, withMapper: mapper) 181 | } 182 | 183 | var mutablePointer = pointer.advanced(by: currentOffset) 184 | mirror.children.forEach({ (child) in 185 | 186 | var offset = 0, size = 0 187 | 188 | guard let propertyType = type(of: child.value) as? Property.Type else { 189 | print("label: ", child.label ?? "", "type: ", "\(type(of: child.value))") 190 | fatalError("Each property should be handyjson-property type") 191 | } 192 | 193 | size = propertyType.size() 194 | offset = propertyType.offsetToAlignment(value: currentOffset, align: propertyType.align()) 195 | 196 | mutablePointer = mutablePointer.advanced(by: offset) 197 | currentOffset += offset 198 | 199 | guard let label = child.label else { 200 | mutablePointer = mutablePointer.advanced(by: size) 201 | currentOffset += size 202 | return 203 | } 204 | 205 | var key = label 206 | 207 | if let converter = mapper.getNameAndConverter(key: mutablePointer.hashValue) { 208 | // if specific key is set, replace the label 209 | if let specifyKey = converter.0 { 210 | key = specifyKey 211 | } 212 | 213 | // if specific converter is set, use it the assign value to the property 214 | if let specifyConverter = converter.1 { 215 | let ocValue = (dict[key] as? NSObject)?.toString() 216 | specifyConverter(ocValue ?? "") 217 | 218 | mutablePointer = mutablePointer.advanced(by: size) 219 | currentOffset += size 220 | return 221 | } 222 | } 223 | 224 | guard let value = dict[key] as? NSObject else { 225 | mutablePointer = mutablePointer.advanced(by: size) 226 | currentOffset += size 227 | return 228 | } 229 | 230 | if let sv = propertyType.valueFrom(object: value) { 231 | propertyType.codeIntoMemory(pointer: mutablePointer, value: sv) 232 | } 233 | 234 | mutablePointer = mutablePointer.advanced(by: size) 235 | currentOffset += size 236 | }) 237 | return currentOffset 238 | } 239 | 240 | public static func _transform(dict: NSDictionary, toType type: HandyJSON.Type) -> HandyJSON { 241 | var instance = type.init() 242 | let mirror = Mirror(reflecting: instance) 243 | 244 | guard let dStyle = mirror.displayStyle else { 245 | fatalError("Target type must has a display type") 246 | } 247 | 248 | var pointer: UnsafeMutablePointer! 249 | let mapper = HelpingMapper() 250 | var currentOffset = 0 251 | 252 | // do user-specified mapping first 253 | instance.mapping(mapper: mapper) 254 | 255 | if dStyle == .class { 256 | pointer = instance.headPointerOfClass() 257 | // for 64bit architecture, it's 16 258 | // for 32bit architecture, it's 12 259 | currentOffset = 8 + MemoryLayout.size 260 | } else if dStyle == .struct { 261 | pointer = instance.headPointerOfStruct() 262 | } else { 263 | fatalError("Target object must be class or struct") 264 | } 265 | 266 | _ = _transform(rawData: dict, toPointer: pointer, toOffset: currentOffset, byMirror: mirror, withMapper: mapper) 267 | 268 | return instance 269 | } 270 | 271 | static func valueFrom(object: NSObject) -> Self? { 272 | if self is HandyJSONEnum.Type { 273 | 274 | if let initWrapper = (self as? HandyJSONEnum.Type)?.makeInitWrapper() { 275 | if let resultValue = initWrapper.convertToEnum(object: object) { 276 | return resultValue as? Self 277 | } 278 | } 279 | return nil 280 | } else if self is BasePropertyProtocol.Type { 281 | 282 | // base type can be transformed directly 283 | return baseValueFrom(object: object) 284 | } else if self is OptionalTypeProtocol.Type { 285 | 286 | // optional type, we parse the wrapped generic type to construct the value, then wrap it to optional 287 | return (self as! OptionalTypeProtocol.Type).optionalFromNSObject(object: object) as? Self 288 | } else if self is ImplicitlyUnwrappedTypeProtocol.Type { 289 | 290 | // similar to optional 291 | return (self as! ImplicitlyUnwrappedTypeProtocol.Type).implicitlyUnwrappedOptionalFromNSObject(object: object) as? Self 292 | } else if self is ArrayTypeProtocol.Type { 293 | 294 | // we can't retrieve the generic type wrapped by array here, so we go into array extension to do the casting 295 | return (self as! ArrayTypeProtocol.Type).arrayFromNSObject(object: object) as? Self 296 | } else if self is DictionaryTypeProtocol.Type { 297 | 298 | // similar to array 299 | return (self as! DictionaryTypeProtocol.Type).dictionaryFromNSObject(object: object) as? Self 300 | } else if self is NSArray.Type { 301 | 302 | if let arr = object as? NSArray { 303 | return arr as? Self 304 | } 305 | } else if self is NSDictionary.Type { 306 | 307 | if let dict = object as? NSDictionary { 308 | return dict as? Self 309 | } 310 | } else if self is HandyJSON.Type { 311 | 312 | if let dict = object as? NSDictionary { 313 | // nested object, transform recursively 314 | return _transform(dict: dict, toType: self as! HandyJSON.Type) as? Self 315 | } 316 | } 317 | return nil 318 | } 319 | 320 | // base type supported parsing directly 321 | static func baseValueFrom(object: NSObject) -> Self? { 322 | switch self { 323 | case is Int8.Type: 324 | return object.toInt8() as? Self 325 | case is UInt8.Type: 326 | return object.toUInt8() as? Self 327 | case is Int16.Type: 328 | return object.toInt16() as? Self 329 | case is UInt16.Type: 330 | return object.toUInt16() as? Self 331 | case is Int32.Type: 332 | return object.toInt32() as? Self 333 | case is UInt32.Type: 334 | return object.toUInt32() as? Self 335 | case is Int64.Type: 336 | return object.toInt64() as? Self 337 | case is UInt64.Type: 338 | return object.toUInt64() as? Self 339 | case is Bool.Type: 340 | return object.toBool() as? Self 341 | case is Int.Type: 342 | return object.toInt() as? Self 343 | case is UInt.Type: 344 | return object.toUInt() as? Self 345 | case is Float.Type: 346 | return object.toFloat() as? Self 347 | case is Double.Type: 348 | return object.toDouble() as? Self 349 | case is String.Type: 350 | return object.toString() as? Self 351 | case is NSString.Type: 352 | return object.toNSString() as? Self 353 | case is NSNumber.Type: 354 | return object.toNSNumber() as? Self 355 | default: 356 | break 357 | } 358 | return nil 359 | } 360 | 361 | // keep in mind, self type is the same with type of value 362 | static func codeIntoMemory(pointer: UnsafeMutablePointer, value: Property) { 363 | pointer.withMemoryRebound(to: Self.self, capacity: 1, { return $0 }).pointee = value as! Self 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /HandyJSONTests/ClassObjectTest.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2101 Alibaba Group. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Created by zhouzhuo on 8/9/16. 18 | // 19 | 20 | import XCTest 21 | import HandyJSON 22 | 23 | class ClassObjectTest: XCTestCase { 24 | 25 | override func setUp() { 26 | super.setUp() 27 | // Put setup code here. This method is called before the invocation of each test method in the class. 28 | } 29 | 30 | override func tearDown() { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | super.tearDown() 33 | } 34 | 35 | func testSimpleClass() { 36 | class A: HandyJSON { 37 | var name: String? 38 | var id: String? 39 | var height: Int? 40 | 41 | required init() {} 42 | } 43 | 44 | let jsonString = "{\"name\":\"Bob\",\"id\":\"12345\",\"height\":180}" 45 | let a = JSONDeserializer.deserializeFrom(json: jsonString)! 46 | XCTAssert(a.name == "Bob") 47 | XCTAssert(a.id == "12345") 48 | XCTAssert(a.height == 180) 49 | } 50 | 51 | func testClassWithArrayProperty() { 52 | class B: NSObject, HandyJSON { 53 | var id: Int? 54 | var arr1: [Int]? 55 | var arr2: [String]? 56 | 57 | required override init() {} 58 | } 59 | 60 | let jsonString = "{\"id\":123456,\"arr1\":[1,2,3,4,5,6],\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 61 | let b = JSONDeserializer.deserializeFrom(json: jsonString)! 62 | XCTAssert(b.id == 123456) 63 | XCTAssert(b.arr1?.count == 6) 64 | XCTAssert(b.arr2?.count == 5) 65 | XCTAssert(b.arr1?.last == 6) 66 | XCTAssert(b.arr2?.last == "e") 67 | } 68 | 69 | func testClassWithImplicitlyUnwrappedOptionalProperty() { 70 | class C: NSObject, HandyJSON { 71 | var id: Int? 72 | var arr1: [Int?]! 73 | var arr2: [String]? 74 | 75 | required override init() {} 76 | } 77 | 78 | let jsonString = "{\"id\":123456,\"arr1\":[1,2,3,4,5,6],\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 79 | let c = JSONDeserializer.deserializeFrom(json: jsonString)! 80 | XCTAssert(c.id == 123456) 81 | XCTAssert(c.arr1.count == 6) 82 | XCTAssert(c.arr2?.count == 5) 83 | XCTAssert((c.arr1.last ?? 0) == 6) 84 | XCTAssert(c.arr2?.last == "e") 85 | } 86 | 87 | func testClassWithDummyProperty() { 88 | class C: NSObject, HandyJSON { 89 | var id: Int? 90 | var arr1: [Int?]! 91 | var arr2: [String]? 92 | 93 | required override init() {} 94 | } 95 | class D: HandyJSON { 96 | var dummy1: String? 97 | var id: Int! 98 | var arr1: [Int]? 99 | var dummy2: C? 100 | var arr2: [String] = [String]() 101 | var dummy3: C! 102 | 103 | required init() {} 104 | } 105 | 106 | let jsonString = "{\"id\":123456,\"arr1\":[1,2,3,4,5,6],\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 107 | let d = JSONDeserializer.deserializeFrom(json: jsonString)! 108 | XCTAssert(d.id == 123456) 109 | XCTAssert(d.arr1?.count == 6) 110 | XCTAssert(d.arr2.count == 5) 111 | XCTAssert(d.arr1?.last == 6) 112 | XCTAssert(d.arr2.last == "e") 113 | } 114 | 115 | func testClassWithDummyJsonField() { 116 | class E: HandyJSON { 117 | var id: Int? 118 | var arr1: [Int?]! 119 | var arr2: [String]? 120 | 121 | required init() {} 122 | } 123 | 124 | let jsonString = "{\"id\":123456,\"dummy1\":23334,\"arr1\":[1,2,3,4,5,6],\"dummy2\":\"string\",\"arr2\":[\"a\",\"b\",\"c\",\"d\",\"e\"]}" 125 | let e = JSONDeserializer.deserializeFrom(json: jsonString)! 126 | XCTAssert(e.id == 123456) 127 | XCTAssert(e.arr1.count == 6) 128 | XCTAssert(e.arr2?.count == 5) 129 | XCTAssert((e.arr1.last ?? 0) == 6) 130 | XCTAssert(e.arr2?.last == "e") 131 | } 132 | 133 | func testOptionalClass() { 134 | class A: HandyJSON { 135 | var name: String? 136 | var id: String? 137 | var height: Int? 138 | 139 | required init() {} 140 | } 141 | 142 | var jsonString: String? = "{\"name\":\"Bob\",\"id\":\"12345\",\"height\":180}" 143 | let a = JSONDeserializer.deserializeFrom(json: jsonString)! 144 | XCTAssert(a.name == "Bob") 145 | XCTAssert(a.id == "12345") 146 | XCTAssert(a.height == 180) 147 | 148 | jsonString = nil 149 | 150 | if let _ = JSONDeserializer.deserializeFrom(json: jsonString) { 151 | XCTAssert(false) 152 | } else { 153 | XCTAssert(true) 154 | } 155 | } 156 | 157 | func testArrayJSONDeserialization() { 158 | class A: HandyJSON { 159 | var name: String? 160 | var id: String? 161 | var height: Int? 162 | 163 | required init() {} 164 | } 165 | 166 | let jsonArrayString: String? = "[{\"name\":\"Bob\",\"id\":\"1\",\"height\":180}, {\"name\":\"Lily\",\"id\":\"2\",\"height\":150}, {\"name\":\"Lucy\",\"id\":\"3\",\"height\":160}]" 167 | let arr = JSONDeserializer.deserializeModelArrayFrom(json: jsonArrayString)! 168 | XCTAssert(arr[0]?.name == "Bob") 169 | XCTAssert(arr[0]?.id == "1") 170 | XCTAssert(arr[0]?.height == 180) 171 | XCTAssert(arr[1]?.name == "Lily") 172 | XCTAssert(arr[1]?.id == "2") 173 | XCTAssert(arr[1]?.height == 150) 174 | XCTAssert(arr[2]?.name == "Lucy") 175 | XCTAssert(arr[2]?.id == "3") 176 | XCTAssert(arr[2]?.height == 160) 177 | } 178 | 179 | func testArrayJSONDeserializationWithDesignatePath() { 180 | class A: HandyJSON { 181 | var name: String? 182 | var id: String? 183 | var height: Int? 184 | 185 | required init() {} 186 | } 187 | 188 | let jsonArrayString: String? = "{\"result\":{\"data\":[{\"name\":\"Bob\",\"id\":\"1\",\"height\":180},{\"name\":\"Lily\",\"id\":\"2\",\"height\":150},{\"name\":\"Lucy\",\"id\":\"3\",\"height\":160}]}}" 189 | let arr = JSONDeserializer.deserializeModelArrayFrom(json: jsonArrayString, designatedPath: "result.data")! 190 | XCTAssert(arr[0]?.name == "Bob") 191 | XCTAssert(arr[0]?.id == "1") 192 | XCTAssert(arr[0]?.height == 180) 193 | XCTAssert(arr[1]?.name == "Lily") 194 | XCTAssert(arr[1]?.id == "2") 195 | XCTAssert(arr[1]?.height == 150) 196 | XCTAssert(arr[2]?.name == "Lucy") 197 | XCTAssert(arr[2]?.id == "3") 198 | XCTAssert(arr[2]?.height == 160) 199 | } 200 | 201 | func testTypeAdaptationString2Others() { 202 | class F: HandyJSON { 203 | // from corresponding type 204 | var aBool: Bool? 205 | var aFloat: Float? 206 | var aDouble: Double? 207 | var aNSNumber: NSNumber? 208 | var aInt: Int? 209 | var aInt8: Int8? 210 | var aInt16: Int16? 211 | var aInt32: Int32? 212 | var aInt64: Int64? 213 | var aUInt: UInt? 214 | var aUInt8: UInt8? 215 | var aUInt16: UInt16? 216 | var aUInt32: UInt32? 217 | var aUInt64: UInt64? 218 | 219 | // from string 220 | var bBool: Bool? 221 | var bFloat: Float? 222 | var bDouble: Double? 223 | var bNSNumber: NSNumber? 224 | var bInt: Int? 225 | var bInt8: Int8? 226 | var bInt16: Int16? 227 | var bInt32: Int32? 228 | var bInt64: Int64? 229 | var bUInt: UInt? 230 | var bUInt8: UInt8? 231 | var bUInt16: UInt16? 232 | var bUInt32: UInt32? 233 | var bUInt64: UInt64? 234 | 235 | required init() {} 236 | } 237 | 238 | let jsonString = "{\"aBool\":false,\"aFloat\":1.23,\"aDouble\":1.23,\"aNSNumber\":1.23,\"aInt\":-1,\"aInt8\":-1,\"aInt16\":-1,\"aInt32\":-1,\"aInt64\":-1,\"aUInt\":1,\"aUInt8\":1,\"aUInt16\":1,\"aUInt32\":1,\"aUInt64\":1,\"bBool\":\"false\",\"bFloat\":\"1.23\",\"bDouble\":\"1.23\",\"bNSNumber\":\"1.23\",\"bInt\":\"-1\",\"bInt8\":\"-1\",\"bInt16\":\"-1\",\"bInt32\":\"-1\",\"bInt64\":\"-1\",\"bUInt\":\"1\",\"bUInt8\":\"1\",\"bUInt16\":\"1\",\"bUInt32\":\"1\",\"bUInt64\":\"1\"}" 239 | let model = JSONDeserializer.deserializeFrom(json: jsonString)! 240 | XCTAssertTrue(model.aBool == false) 241 | XCTAssertTrue(model.aFloat == 1.23) 242 | XCTAssertTrue(model.aDouble == 1.23) 243 | XCTAssertTrue(model.aNSNumber == 1.23) 244 | XCTAssertTrue(model.aInt == -1) 245 | XCTAssertTrue(model.aInt8 == -1) 246 | XCTAssertTrue(model.aInt16 == -1) 247 | XCTAssertTrue(model.aInt32 == -1) 248 | XCTAssertTrue(model.aInt64 == -1) 249 | XCTAssertTrue(model.aUInt == 1) 250 | XCTAssertTrue(model.aUInt8 == 1) 251 | XCTAssertTrue(model.aUInt16 == 1) 252 | XCTAssertTrue(model.aUInt32 == 1) 253 | XCTAssertTrue(model.aUInt64 == 1) 254 | XCTAssertTrue(model.bBool == false) 255 | XCTAssertTrue(model.bFloat == 1.23) 256 | XCTAssertTrue(model.bDouble == 1.23) 257 | XCTAssertTrue(model.bNSNumber == 1.23) 258 | XCTAssertTrue(model.bInt == -1) 259 | XCTAssertTrue(model.bInt8 == -1) 260 | XCTAssertTrue(model.bInt16 == -1) 261 | XCTAssertTrue(model.bInt32 == -1) 262 | XCTAssertTrue(model.bInt64 == -1) 263 | XCTAssertTrue(model.bUInt == 1) 264 | XCTAssertTrue(model.bUInt8 == 1) 265 | XCTAssertTrue(model.bUInt16 == 1) 266 | XCTAssertTrue(model.bUInt32 == 1) 267 | XCTAssertTrue(model.bUInt64 == 1) 268 | } 269 | 270 | func testTypeAdaptationOthers2String() { 271 | class G: HandyJSON { 272 | // to string 273 | var aBool: String? 274 | var aFloat: String? 275 | var aDouble: String? 276 | var aNSNumber: String? 277 | var aInt: String? 278 | var aInt8: String? 279 | var aInt16: String? 280 | var aInt32: String? 281 | var aInt64: String? 282 | var aUInt: String? 283 | var aUInt8: String? 284 | var aUInt16: String? 285 | var aUInt32: String? 286 | var aUInt64: String? 287 | 288 | // to nsstring 289 | var bBool: NSString? 290 | var bFloat: NSString? 291 | var bDouble: NSString? 292 | var bNSNumber: NSString? 293 | var bInt: NSString? 294 | var bInt8: NSString? 295 | var bInt16: NSString? 296 | var bInt32: NSString? 297 | var bInt64: NSString? 298 | var bUInt: NSString? 299 | var bUInt8: NSString? 300 | var bUInt16: NSString? 301 | var bUInt32: NSString? 302 | var bUInt64: NSString? 303 | 304 | required init() {} 305 | } 306 | 307 | let jsonString = "{\"aBool\":false,\"aFloat\":1.23,\"aDouble\":1.23,\"aNSNumber\":1.23,\"aInt\":-1,\"aInt8\":-1,\"aInt16\":-1,\"aInt32\":-1,\"aInt64\":-1,\"aUInt\":1,\"aUInt8\":1,\"aUInt16\":1,\"aUInt32\":1,\"aUInt64\":1,\"bBool\":false,\"bFloat\":1.23,\"bDouble\":1.23,\"bNSNumber\":1.23,\"bInt\":-1,\"bInt8\":-1,\"bInt16\":-1,\"bInt32\":-1,\"bInt64\":-1,\"bUInt\":1,\"bUInt8\":1,\"bUInt16\":1,\"bUInt32\":1,\"bUInt64\":1}" 308 | let model = JSONDeserializer.deserializeFrom(json: jsonString)! 309 | XCTAssertTrue(model.aBool == "false") 310 | XCTAssertTrue(model.aFloat == "1.23") 311 | XCTAssertTrue(model.aDouble == "1.23") 312 | XCTAssertTrue(model.aNSNumber == "1.23") 313 | XCTAssertTrue(model.aInt == "-1") 314 | XCTAssertTrue(model.aInt8 == "-1") 315 | XCTAssertTrue(model.aInt16 == "-1") 316 | XCTAssertTrue(model.aInt32 == "-1") 317 | XCTAssertTrue(model.aInt64 == "-1") 318 | XCTAssertTrue(model.aUInt == "1") 319 | XCTAssertTrue(model.aUInt8 == "1") 320 | XCTAssertTrue(model.aUInt16 == "1") 321 | XCTAssertTrue(model.aUInt32 == "1") 322 | XCTAssertTrue(model.aUInt64 == "1") 323 | XCTAssertTrue(model.bBool == "false") 324 | XCTAssertTrue(model.bFloat == "1.23") 325 | XCTAssertTrue(model.bDouble == "1.23") 326 | XCTAssertTrue(model.bNSNumber == "1.23") 327 | XCTAssertTrue(model.bInt == "-1") 328 | XCTAssertTrue(model.bInt8 == "-1") 329 | XCTAssertTrue(model.bInt16 == "-1") 330 | XCTAssertTrue(model.bInt32 == "-1") 331 | XCTAssertTrue(model.bInt64 == "-1") 332 | XCTAssertTrue(model.bUInt == "1") 333 | XCTAssertTrue(model.bUInt8 == "1") 334 | XCTAssertTrue(model.bUInt16 == "1") 335 | XCTAssertTrue(model.bUInt32 == "1") 336 | XCTAssertTrue(model.bUInt64 == "1") 337 | } 338 | 339 | func testTypeAdaptationNSNull2Others() { 340 | class H: HandyJSON { 341 | // from corresponding type 342 | var aBool: Bool? 343 | var aFloat: Float? 344 | var aDouble: Double? 345 | var aNSNumber: NSNumber? 346 | var aInt: Int? 347 | var aInt8: Int8? 348 | var aInt16: Int16? 349 | var aInt32: Int32? 350 | var aInt64: Int64? 351 | var aUInt: UInt? 352 | var aUInt8: UInt8? 353 | var aUInt16: UInt16? 354 | var aUInt32: UInt32? 355 | var aUInt64: UInt64? 356 | 357 | required init() {} 358 | } 359 | 360 | let jsonString = "{\"aBool\":null,\"aFloat\":null,\"aDouble\":null,\"aNSNumber\":null,\"aInt\":null,\"aInt8\":null,\"aInt16\":null,\"aInt32\":null,\"aInt64\":null,\"aUInt\":null,\"aUInt8\":null,\"aUInt16\":null,\"aUInt32\":null,\"aUInt64\":null}" 361 | let model = JSONDeserializer.deserializeFrom(json: jsonString)! 362 | XCTAssertNil(model.aBool) 363 | XCTAssertNil(model.aFloat) 364 | XCTAssertNil(model.aDouble) 365 | XCTAssertNil(model.aNSNumber) 366 | XCTAssertNil(model.aInt) 367 | XCTAssertNil(model.aInt8) 368 | XCTAssertNil(model.aInt16) 369 | XCTAssertNil(model.aInt32) 370 | XCTAssertNil(model.aInt64) 371 | XCTAssertNil(model.aUInt) 372 | XCTAssertNil(model.aUInt8) 373 | XCTAssertNil(model.aUInt16) 374 | XCTAssertNil(model.aUInt32) 375 | XCTAssertNil(model.aUInt64) 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HandyJSON 2 | 3 | HandyJSON is a framework written in Swift which to make converting model objects(classes/structs) to and from JSON easy on iOS. 4 | 5 | Compared with others, the most significant feature of HandyJSON is that it does not require the objects inherit from NSObject(**not using KVC but reflection**), neither implements a 'mapping' function(**use pointer to achieve property assignment**). 6 | 7 | **Notice that** , HandyJSON is totally depend on the memory layout rules of Swift which we haven’t found formal specification from Apple(And I'm looking forward to someone can help). The good new is that it has never changed in the past. Also We can adjust out strategy if it really change. So, I think “the potential crisis" is more likely oneday Swift make reflection more powerful, such as supporting assignment. 8 | 9 | [![Build Status](https://travis-ci.org/alibaba/HandyJSON.svg?branch=master)](https://travis-ci.org/alibaba/HandyJSON) 10 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 11 | [![Cocoapods Version](https://img.shields.io/cocoapods/v/HandyJSON.svg?style=flat)](http://cocoadocs.org/docsets/HandyJSON) 12 | [![Cocoapods Platform](https://img.shields.io/cocoapods/p/HandyJSON.svg?style=flat)](http://cocoadocs.org/docsets/HandyJSON) 13 | [![Codecov branch](https://img.shields.io/codecov/c/github/alibaba/HandyJSON/master.svg?style=flat)](https://codecov.io/gh/alibaba/HandyJSON/branch/master) 14 | 15 | ## [中文文档](./README_cn.md) 16 | 17 | ## Sample Code 18 | 19 | ### Deserialization 20 | 21 | ```swift 22 | class Animal: HandyJSON { 23 | var name: String? 24 | var count: Int? 25 | 26 | required init() {} 27 | } 28 | 29 | let json = "{\"name\": \"Cat\", \"count\": 5}" 30 | 31 | if let cat = JSONDeserializer.deserializeFrom(json: json) { 32 | print(cat) 33 | } 34 | ``` 35 | 36 | ### Serialization 37 | 38 | ```swift 39 | class Animal { 40 | var name: String? 41 | var count: Int? 42 | 43 | init(name: String, count: Int) { 44 | self.name = name 45 | self.count = count 46 | } 47 | } 48 | 49 | let cat = Animal(name: "cat", count: 5) 50 | 51 | print(JSONSerializer.serialize(model: cat).toJSON()!) 52 | print(JSONSerializer.serialize(model: cat).toPrettifyJSON()!) 53 | print(JSONSerializer.serialize(model: cat).toSimpleDictionary()!) 54 | ``` 55 | 56 | # Content 57 | 58 | - [Features](#features) 59 | - [Requirements](#requirements) 60 | - [Installation](#installation) 61 | - [Cocoapods](#cocoapods) 62 | - [Carthage](#carthage) 63 | - [Manually](#manually) 64 | - [Deserialization](#deserialization) 65 | - [The Basics](#the-basics) 66 | - [Support Struct](#support-struct) 67 | - [Support Enum Property](#support-enum-property) 68 | - [Optional, ImplicitlyUnwrappedOptional, Collections and so on](#optional-implicitlyunwrappedoptional-collections-and-so-on) 69 | - [Designated Path](#designated-path) 70 | - [Composition Object](#composition-object) 71 | - [Inheritance Object](#inheritance-object) 72 | - [Array In JSON](#array-in-json) 73 | - [Custom Mapping](#custom-mapping) 74 | - [Supported Property Type](#supported-property-type) 75 | - [Serialization](#serialization) 76 | - [The Basics](#the-basics) 77 | - [Complex Object](#complex-object) 78 | - [To Do](#to-do) 79 | 80 | # Features 81 | 82 | * Serialize/Deserialize Object/JSON to/From JSON/Object 83 | 84 | * Naturally use object property name for mapping, no need to specify a mapping relationship 85 | 86 | * Support almost all types in Swift, including enum 87 | 88 | * Support struct 89 | 90 | * Custom transformations for mapping 91 | 92 | * Type-Adaption, such as string json field maps to int property, int json field maps to string property 93 | 94 | # Requirements 95 | 96 | * iOS 8.0+/OSX 10.9+/watchOS 2.0+/tvOS 9.0+ 97 | 98 | * Swift 2.3+ / Swift 3.0+ 99 | 100 | # Installation 101 | 102 | **To use with Swift 2.x using == 0.4.0** 103 | 104 | **To use with Swift 3.x using >= 1.3.0** 105 | 106 | For Legacy Swift support, take a look at the [swift2 branch](https://github.com/alibaba/HandyJSON/tree/master_for_swift_2x). 107 | 108 | ## Cocoapods 109 | 110 | Add the following line to your `Podfile`: 111 | 112 | ``` 113 | pod 'HandyJSON', '~> 1.3.0' 114 | ``` 115 | 116 | Then, run the following command: 117 | 118 | ``` 119 | $ pod install 120 | ``` 121 | 122 | ## Carthage 123 | 124 | You can add a dependency on `HandyJSON` by adding the following line to your `Cartfile`: 125 | 126 | ``` 127 | github "alibaba/HandyJSON" ~> 1.3.0 128 | ``` 129 | 130 | ## Manually 131 | 132 | You can integrate `HandyJSON` into your project manually by doing the following steps: 133 | 134 | * Open up `Terminal`, `cd` into your top-level project directory, and add `HandyJSON` as a submodule: 135 | 136 | ``` 137 | git init && git submodule add https://github.com/alibaba/HandyJSON.git 138 | ``` 139 | 140 | * Open the new `HandyJSON` folder, drag the `HandyJSON.xcodeproj` into the `Project Navigator` of your project. 141 | 142 | * Select your application project in the `Project Navigator`, open the `General` panel in the right window. 143 | 144 | * Click on the `+` button under the `Embedded Binaries` section. 145 | 146 | * You will see two different `HandyJSON.xcodeproj` folders each with four different versions of the HandyJSON.framework nested inside a Products folder. 147 | > It does not matter which Products folder you choose from, but it does matter which HandyJSON.framework you choose. 148 | 149 | * Select one of the four `HandyJSON.framework` which matches the platform your Application should run on. 150 | 151 | * Congratulations! 152 | 153 | # Deserialization 154 | 155 | ## The Basics 156 | 157 | To support deserialization from JSON, a class/struct need to conform to 'HandyJSON' protocol. It's truly protocol, not some class inherited from NSObject. 158 | 159 | To conform to 'HandyJSON', a class need to implement an empty initializer. 160 | 161 | ```swift 162 | class Animal: HandyJSON { 163 | var name: String? 164 | var id: String? 165 | var num: Int? 166 | 167 | required init() {} 168 | } 169 | 170 | let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}" 171 | 172 | if let animal = JSONDeserializer.deserializeFrom(json: jsonString) { 173 | print(animal) 174 | } 175 | ``` 176 | 177 | ## Support Struct 178 | 179 | For struct, since the compiler provide a default empty initializer, we use it for free. 180 | 181 | ```swift 182 | struct Animal: HandyJSON { 183 | var name: String? 184 | var id: String? 185 | var num: Int? 186 | } 187 | 188 | let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}" 189 | 190 | if let animal = JSONDeserializer.deserializeFrom(json: jsonString) { 191 | print(animal) 192 | } 193 | ``` 194 | 195 | But also notice that, if you have a designated initializer to override the default one in the struct, you should explicitly declare an empty one(no `required` modifier need). 196 | 197 | ## Support Enum Property 198 | 199 | Limited by some type converting problems, supporting `enum` type is a little special here. To be convertable, An `enum` must conform to `HandyJSONEnum` protocol and implement a `makeInitWrapper` function. 200 | 201 | ```swift 202 | enum AnimalType: String, HandyJSONEnum { 203 | case Cat = "cat" 204 | case Dog = "dog" 205 | case Bird = "bird" 206 | 207 | static func makeInitWrapper() -> InitWrapperProtocol? { 208 | return InitWrapper(rawInit: AnimalType.init) 209 | } 210 | } 211 | 212 | class Animal: HandyJSON { 213 | var type: AnimalType? 214 | var name: String? 215 | 216 | required init() {} 217 | } 218 | 219 | let jsonString = "{\"type\":\"cat\",\"name\":\"Tom\"}" 220 | if let animal = JSONDeserializer.deserializeFrom(json: jsonString) { 221 | print(animal) 222 | } 223 | ``` 224 | 225 | It’s not that troublesome. Just wrap the `init` funcion of a `RawRepresentable` enum and return. You can even do this in an extension. 226 | 227 | ```swift 228 | enum AnimalType: String { 229 | case Cat = "cat" 230 | case Dog = "dog" 231 | case Bird = "bird" 232 | } 233 | 234 | extension AnimalType: HandyJSONEnum { 235 | static func makeInitWrapper() -> InitWrapperProtocol? { 236 | return InitWrapper(rawInit: AnimalType.init) 237 | } 238 | } 239 | 240 | ... 241 | ``` 242 | 243 | Now it’s totally non-intrusive. 244 | 245 | ## Optional, ImplicitlyUnwrappedOptional, Collections and so on 246 | 247 | 'HandyJSON' support classes/structs composed of `optional`, `implicitlyUnwrappedOptional`, `array`, `dictionary`, `objective-c base type`, `nested type` etc. properties. 248 | 249 | ```swift 250 | class Cat: HandyJSON { 251 | var id: Int64! 252 | var name: String! 253 | var friend: [String]? 254 | var weight: Double? 255 | var alive: Bool = true 256 | var color: NSString? 257 | 258 | required init() {} 259 | } 260 | 261 | let jsonString = "{\"id\":1234567,\"name\":\"Kitty\",\"friend\":[\"Tom\",\"Jack\",\"Lily\",\"Black\"],\"weight\":15.34,\"alive\":false,\"color\":\"white\"}" 262 | 263 | if let cat = JSONDeserializer.deserializeFrom(json: jsonString) { 264 | print(cat) 265 | } 266 | ``` 267 | 268 | ## Designated Path 269 | 270 | `HandyJSON` supports deserialization from designated path of JSON. 271 | 272 | ```swift 273 | class Cat: HandyJSON { 274 | var id: Int64! 275 | var name: String! 276 | 277 | required init() {} 278 | } 279 | 280 | let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}" 281 | 282 | if let cat = JSONDeserializer.deserializeFrom(json: jsonString, designatedPath: "data.cat") { 283 | print(cat.name) 284 | } 285 | ``` 286 | 287 | ## Composition Object 288 | 289 | Notice that all the properties of a class/struct need to deserialized should be type conformed to `HandyJSON`. 290 | 291 | ```swift 292 | class Component: HandyJSON { 293 | var aInt: Int? 294 | var aString: String? 295 | 296 | required init() {} 297 | } 298 | 299 | class Composition: HandyJSON { 300 | var aInt: Int? 301 | var comp1: Component? 302 | var comp2: Component? 303 | 304 | required init() {} 305 | } 306 | 307 | let jsonString = "{\"num\":12345,\"comp1\":{\"aInt\":1,\"aString\":\"aaaaa\"},\"comp2\":{\"aInt\":2,\"aString\":\"bbbbb\"}}" 308 | 309 | if let composition = JSONDeserializer.deserializeFrom(json: jsonString) { 310 | print(composition) 311 | } 312 | ``` 313 | 314 | ## Inheritance Object 315 | 316 | A subclass need deserialization, it's superclass need to conform to `HandyJSON`. 317 | 318 | ```swift 319 | class Animal: HandyJSON { 320 | var id: Int? 321 | var color: String? 322 | 323 | required init() {} 324 | } 325 | 326 | 327 | class Cat: Animal { 328 | var name: String? 329 | 330 | required init() {} 331 | } 332 | 333 | let jsonString = "{\"id\":12345,\"color\":\"black\",\"name\":\"cat\"}" 334 | 335 | if let cat = JSONDeserializer.deserializeFrom(json: jsonString) { 336 | print(cat) 337 | } 338 | ``` 339 | 340 | ## Array In JSON 341 | 342 | If the first level of a JSON text is an array, we turn it to objects array. 343 | 344 | ```swift 345 | class Cat: HandyJSON { 346 | var name: String? 347 | var id: String? 348 | 349 | required init() {} 350 | } 351 | 352 | let jsonArrayString: String? = "[{\"name\":\"Bob\",\"id\":\"1\"}, {\"name\":\"Lily\",\"id\":\"2\"}, {\"name\":\"Lucy\",\"id\":\"3\"}]" 353 | if let cats = JSONDeserializer.deserializeModelArrayFrom(json: jsonArrayString) { 354 | cats.forEach({ (cat) in 355 | if let _cat = cat { 356 | print(_cat.id ?? "", _cat.name ?? "") 357 | } 358 | }) 359 | } 360 | ``` 361 | 362 | ## Custom Mapping 363 | 364 | `HandyJSON` let you customize the key mapping to JSON fields, or parsing method of any property. All you need to do is implementing an optional `mapping` function, do things in it. 365 | 366 | ```swift 367 | class Cat: HandyJSON { 368 | var id: Int64! 369 | var name: String! 370 | var parent: (String, String)? 371 | 372 | required init() {} 373 | 374 | func mapping(mapper: HelpingMapper) { 375 | // specify 'cat_id' field in json map to 'id' property in object 376 | mapper.specify(property: &id, name: "cat_id") 377 | 378 | // specify 'parent' field in json parse as following to 'parent' property in object 379 | mapper.specify(property: &parent, converter: { (rawString) -> (String, String) in 380 | let parentNames = rawString.characters.split{$0 == "/"}.map(String.init) 381 | return (parentNames[0], parentNames[1]) 382 | }) 383 | } 384 | } 385 | 386 | let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\"}" 387 | 388 | if let cat = JSONDeserializer.deserializeFrom(json: jsonString) { 389 | print(cat) 390 | } 391 | ``` 392 | 393 | ## Supported Property Type 394 | 395 | * `Int`/`Bool`/`Double`/`Float`/`String`/`NSNumber`/`NSString` 396 | 397 | * `RawRepresentable` enum 398 | 399 | * `NSArray/NSDictionary` 400 | 401 | * `Int8/Int16/Int32/Int64`/`UInt8/UInt16/UInt23/UInt64` 402 | 403 | * `Optional/ImplicitUnwrappedOptional` // T is one of the above types 404 | 405 | * `Array` // T is one of the above types 406 | 407 | * `Dictionary` // T is one of the above types 408 | 409 | * Nested of aboves 410 | 411 | # Serialization 412 | 413 | ## The Basics 414 | 415 | You need to do nothing special to support serialization. Define the class/struct, get the instances, then serialize it to json text, or simple dictionary. 416 | 417 | ```swift 418 | class Animal { 419 | var name: String? 420 | var height: Int? 421 | 422 | init(name: String, height: Int) { 423 | self.name = name 424 | self.height = height 425 | } 426 | } 427 | 428 | let cat = Animal(name: "cat", height: 30) 429 | if let jsonStr = JSONSerializer.serialize(model: cat).toJSON() { 430 | print("simple json string: ", jsonStr) 431 | } 432 | if let prettifyJSON = JSONSerializer.serialize(model: cat).toPrettifyJSON() { 433 | print("prettify json string: ", prettifyJSON) 434 | } 435 | if let dict = JSONSerializer.serialize(model: cat).toSimpleDictionary() { 436 | print("dictionary: ", dict) 437 | } 438 | ``` 439 | 440 | ## Complex Object 441 | 442 | Still need no extra effort. 443 | 444 | ```swift 445 | enum Gender { 446 | case Male 447 | case Female 448 | } 449 | 450 | struct Subject { 451 | var id: Int64? 452 | var name: String? 453 | 454 | init(id: Int64, name: String) { 455 | self.id = id 456 | self.name = name 457 | } 458 | } 459 | 460 | class Student { 461 | var name: String? 462 | var gender: Gender? 463 | var subjects: [Subject]? 464 | } 465 | 466 | let student = Student() 467 | student.name = "Jack" 468 | student.gender = .Female 469 | student.subjects = [Subject(id: 1, name: "math"), Subject(id: 2, name: "English"), Subject(id: 3, name: "Philosophy")] 470 | 471 | if let jsonStr = JSONSerializer.serialize(model: student).toJSON() { 472 | print("simple json string: ", jsonStr) 473 | } 474 | if let prettifyJSON = JSONSerializer.serialize(model: student).toPrettifyJSON() { 475 | print("prettify json string: ", prettifyJSON) 476 | } 477 | if let dict = JSONSerializer.serialize(model: student).toSimpleDictionary() { 478 | print("dictionary: ", dict) 479 | } 480 | ``` 481 | 482 | # To Do 483 | 484 | * Improve testcases 485 | 486 | * Improve error handling 487 | 488 | * Support non-object (such as basic type, array, dictionany) type deserializing directly (will not support) 489 | 490 | # License 491 | 492 | HandyJSON is released under the Apache License, Version 2.0. See LICENSE for details. 493 | --------------------------------------------------------------------------------