├── .swift-version ├── qq_group.png ├── Package.swift ├── .gitignore ├── Package@swift-4.swift ├── Source ├── Export.swift ├── MangledName.swift ├── Info-tvOS.plist ├── Info-watchOS.plist ├── EnumType.swift ├── Info-iOS.plist ├── PropertyInfo.swift ├── Info-macOS.plist ├── ExtendCustomBasicType.swift ├── HandyJSON.h ├── LICENSE ├── PointerType.swift ├── CBridge.swift ├── ReflectionHelper.swift ├── TransformType.swift ├── Configuration.swift ├── Logger.swift ├── ISO8601DateTransform.swift ├── CustomDateFormatTransform.swift ├── Transformable.swift ├── EnumTransform.swift ├── DataTransform.swift ├── TransformOf.swift ├── DateTransform.swift ├── DateFormatterTransform.swift ├── NSDecimalNumberTransform.swift ├── BuiltInBridgeType.swift ├── OtherExtension.swift ├── URLTransform.swift ├── Properties.swift ├── FieldDescriptor.swift ├── Serializer.swift ├── AnyExtensions.swift ├── Measuable.swift ├── HexColorTransform.swift ├── ContextDescriptorType.swift ├── BuiltInBasicType.swift ├── Deserializer.swift ├── HelpingMapper.swift └── ExtendCustomModelType.swift ├── Tests └── HandyJSONTests │ ├── OCUIInheritanceClass.swift │ ├── Info-macOS.plist │ ├── Info-tvOS.plist │ ├── Info-iOS.plist │ ├── OCUIInheritanceTests.swift │ ├── GenericTypes.swift │ ├── NestTypes.swift │ ├── InvalidStateHandlingTest.swift │ ├── TestUtils.swift │ ├── BasicTypesInStruct.swift │ ├── CustomTransfromTypes.swift │ ├── GenericTypesTest.swift │ ├── BasicTypesInClass.swift │ ├── CustomTransformTest.swift │ └── CustomMappingTest.swift ├── HandyJSON.podspec ├── HandyJSONDemo ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── ViewController.swift └── AppDelegate.swift ├── .travis.yml ├── 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 ├── LICENSE └── CHANGELOG.md /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /qq_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/HandyJSON/HEAD/qq_group.png -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "HandyJSON" 5 | ) 6 | 7 | -------------------------------------------------------------------------------- /.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 | Products/ 20 | -------------------------------------------------------------------------------- /Package@swift-4.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "HandyJSON", 6 | products: [ 7 | .library(name: "HandyJSON", targets: ["HandyJSON"]), 8 | ], 9 | targets: [ 10 | .target( 11 | name: "HandyJSON", 12 | dependencies: [], 13 | path: "Source" 14 | ) 15 | ] 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /Source/Export.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Export.swift 3 | // HandyJSON 4 | // 5 | // Created by zhouzhuo on 16/07/2017. 6 | // Copyright © 2017 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol HandyJSONCustomTransformable: _ExtendCustomBasicType {} 12 | 13 | public protocol HandyJSON: _ExtendCustomModelType {} 14 | 15 | public protocol HandyJSONEnum: _RawEnumProtocol {} 16 | -------------------------------------------------------------------------------- /Source/MangledName.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MangledName.swift 3 | // HandyJSON 4 | // 5 | // Created by chantu on 2019/2/2. 6 | // Copyright © 2019 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // mangled name might contain 0 but it is not the end, do not just use strlen 12 | func getMangledTypeNameSize(_ mangledName: UnsafePointer) -> Int { 13 | // TODO: should find the actually size 14 | return 256 15 | } 16 | -------------------------------------------------------------------------------- /Tests/HandyJSONTests/OCUIInheritanceClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OCUIInheritanceClass.swift 3 | // HandyJSON iOS Tests 4 | // 5 | // Created by chantu on 2020/2/24. 6 | // Copyright © 2020 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import HandyJSON 12 | 13 | class InheritFromUIViewControllerClass: UIViewController, HandyJSON { 14 | 15 | var a: Int? 16 | var b: String = "" 17 | } 18 | 19 | class SubClassOfInheritFromUIViewController: InheritFromUIViewControllerClass { 20 | var c: Double? 21 | } 22 | -------------------------------------------------------------------------------- /Tests/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 | -------------------------------------------------------------------------------- /Tests/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 | -------------------------------------------------------------------------------- /Tests/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 | -------------------------------------------------------------------------------- /Source/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 | -------------------------------------------------------------------------------- /Source/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 = '5.0.4-beta' 7 | s.homepage = "https://github.com/alibaba/handyjson" 8 | s.name = "HandyJSON" 9 | 10 | s.source_files = 'Source/**/*.{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' => '5.0' } 22 | end 23 | -------------------------------------------------------------------------------- /Source/EnumType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnumType.swift 3 | // HandyJSON 4 | // 5 | // Created by zhouzhuo on 16/07/2017. 6 | // Copyright © 2017 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol _RawEnumProtocol: _Transformable { 12 | 13 | static func _transform(from object: Any) -> Self? 14 | func _plainValue() -> Any? 15 | } 16 | 17 | extension RawRepresentable where Self: _RawEnumProtocol { 18 | 19 | public static func _transform(from object: Any) -> Self? { 20 | if let transformableType = RawValue.self as? _Transformable.Type { 21 | if let typedValue = transformableType.transform(from: object) { 22 | return Self(rawValue: typedValue as! RawValue) 23 | } 24 | } 25 | return nil 26 | } 27 | 28 | public func _plainValue() -> Any? { 29 | return self.rawValue 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Source/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 | -------------------------------------------------------------------------------- /Source/PropertyInfo.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 | // PropertyInfo.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 20/08/2017. 22 | // 23 | 24 | struct PropertyInfo { 25 | let key: String 26 | let type: Any.Type 27 | let address: UnsafeMutableRawPointer 28 | let bridged: Bool 29 | } 30 | -------------------------------------------------------------------------------- /Source/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 | -------------------------------------------------------------------------------- /Source/ExtendCustomBasicType.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 | // ExtendCustomBasicType.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 05/09/2017. 22 | // 23 | 24 | public protocol _ExtendCustomBasicType: _Transformable { 25 | 26 | static func _transform(from object: Any) -> Self? 27 | func _plainValue() -> Any? 28 | } 29 | -------------------------------------------------------------------------------- /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 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Source/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 | -------------------------------------------------------------------------------- /Source/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | ## Reflection 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2016 Brad Hilton 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /Source/PointerType.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 | // Created by zhouzhuo on 07/01/2017. 19 | // 20 | 21 | protocol PointerType : Equatable { 22 | associatedtype Pointee 23 | var pointer: UnsafePointer { get set } 24 | } 25 | 26 | extension PointerType { 27 | init(pointer: UnsafePointer) { 28 | func cast(_ value: T) -> U { 29 | return unsafeBitCast(value, to: U.self) 30 | } 31 | self = cast(UnsafePointer(pointer)) 32 | } 33 | } 34 | 35 | func == (lhs: T, rhs: T) -> Bool { 36 | return lhs.pointer == rhs.pointer 37 | } 38 | -------------------------------------------------------------------------------- /Source/CBridge.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 | // CBridge.swift 19 | // HandyJSON 20 | // 21 | // Created by chantu on 2018/7/15. 22 | // Copyright © 2018 aliyun. All rights reserved. 23 | // 24 | 25 | import Foundation 26 | 27 | @_silgen_name("swift_getTypeByMangledNameInContext") 28 | public func _getTypeByMangledNameInContext( 29 | _ name: UnsafePointer, 30 | _ nameLength: Int, 31 | genericContext: UnsafeRawPointer?, 32 | genericArguments: UnsafeRawPointer?) 33 | -> Any.Type? 34 | 35 | 36 | @_silgen_name("swift_getTypeContextDescriptor") 37 | public func _swift_getTypeContextDescriptor(_ metadata: UnsafeRawPointer?) -> UnsafeRawPointer? 38 | -------------------------------------------------------------------------------- /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 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Tests/HandyJSONTests/OCUIInheritanceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OCUIInheritanceTests.swift 3 | // HandyJSON iOS Tests 4 | // 5 | // Created by chantu on 2020/2/24. 6 | // Copyright © 2020 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | import HandyJSON 12 | 13 | class OCUIInheritanceTests: XCTestCase { 14 | 15 | func testInheritFromUIViewController() { 16 | let obj = InheritFromUIViewControllerClass() 17 | obj.a = 0x1234 18 | obj.b = "hehe" 19 | 20 | let JSONString = obj.toJSONString(prettyPrint: true) 21 | let mappedObject = JSONDeserializer.deserializeFrom(json: JSONString!) 22 | 23 | XCTAssertNotNil(mappedObject) 24 | XCTAssertEqual(mappedObject!.a, 0x1234) 25 | XCTAssertEqual(mappedObject!.b, "hehe") 26 | } 27 | 28 | func testSubClassOfInheritFromUIViewController() { 29 | let obj = SubClassOfInheritFromUIViewController() 30 | obj.a = 0x1234 31 | obj.b = "hehe" 32 | obj.c = 0.5678 33 | 34 | let JSONString = obj.toJSONString(prettyPrint: true) 35 | let mappedObject = JSONDeserializer.deserializeFrom(json: JSONString!) 36 | 37 | XCTAssertNotNil(mappedObject) 38 | XCTAssertEqual(mappedObject!.a, 0x1234) 39 | XCTAssertEqual(mappedObject!.b, "hehe") 40 | XCTAssertEqual(mappedObject!.c, 0.5678) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/ReflectionHelper.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 | // Helper.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 07/01/2017. 22 | // 23 | 24 | struct ReflectionHelper { 25 | 26 | static func mutableStorage(instance: inout T) -> UnsafeMutableRawPointer { 27 | return UnsafeMutableRawPointer(mutating: storage(instance: &instance)) 28 | } 29 | 30 | static func storage(instance: inout T) -> UnsafeRawPointer { 31 | if type(of: instance) is AnyClass { 32 | let opaquePointer = Unmanaged.passUnretained(instance as AnyObject).toOpaque() 33 | return UnsafeRawPointer(opaquePointer) 34 | } else { 35 | return withUnsafePointer(to: &instance) { pointer in 36 | return UnsafeRawPointer(pointer) 37 | } 38 | } 39 | } 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /Source/TransformType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformType.swift 3 | // ObjectMapper 4 | // 5 | // Created by Syo Ikeda on 2/4/15. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | public protocol TransformType { 30 | associatedtype Object 31 | associatedtype JSON 32 | 33 | func transformFromJSON(_ value: Any?) -> Object? 34 | func transformToJSON(_ value: Object?) -> JSON? 35 | } 36 | -------------------------------------------------------------------------------- /Source/Configuration.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 | // Configuration.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 08/01/2017. 22 | // 23 | 24 | public struct DeserializeOptions: OptionSet { 25 | public let rawValue: Int 26 | 27 | public static let caseInsensitive = DeserializeOptions(rawValue: 1 << 0) 28 | 29 | public static let defaultOptions: DeserializeOptions = [] 30 | 31 | public init(rawValue: Int) { 32 | self.rawValue = rawValue 33 | } 34 | } 35 | 36 | public enum DebugMode: Int { 37 | case verbose = 0 38 | case debug = 1 39 | case error = 2 40 | case none = 3 41 | } 42 | 43 | public struct HandyJSONConfiguration { 44 | 45 | private static var _mode = DebugMode.error 46 | public static var debugMode: DebugMode { 47 | get { 48 | return _mode 49 | } 50 | set { 51 | _mode = newValue 52 | } 53 | } 54 | 55 | public static var deserializeOptions: DeserializeOptions = .defaultOptions 56 | } 57 | -------------------------------------------------------------------------------- /Source/Logger.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 | // Logger.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 08/01/2017. 22 | // 23 | 24 | struct InternalLogger { 25 | 26 | static func logError(_ items: Any..., separator: String = " ", terminator: String = "\n") { 27 | if HandyJSONConfiguration.debugMode.rawValue <= DebugMode.error.rawValue { 28 | print(items, separator: separator, terminator: terminator) 29 | } 30 | } 31 | 32 | static func logDebug(_ items: Any..., separator: String = " ", terminator: String = "\n") { 33 | if HandyJSONConfiguration.debugMode.rawValue <= DebugMode.debug.rawValue { 34 | print(items, separator: separator, terminator: terminator) 35 | } 36 | } 37 | 38 | static func logVerbose(_ items: Any..., separator: String = " ", terminator: String = "\n") { 39 | if HandyJSONConfiguration.debugMode.rawValue <= DebugMode.verbose.rawValue { 40 | print(items, separator: separator, terminator: terminator) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Source/ISO8601DateTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ISO8601DateTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Jean-Pierre Mouilleseaux on 21 Nov 2014. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class ISO8601DateTransform: DateFormatterTransform { 32 | 33 | public init() { 34 | let formatter = DateFormatter() 35 | formatter.locale = Locale(identifier: "en_US_POSIX") 36 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" 37 | 38 | super.init(dateFormatter: formatter) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Source/CustomDateFormatTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomDateFormatTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Dan McCracken on 3/8/15. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class CustomDateFormatTransform: DateFormatterTransform { 32 | 33 | public init(formatString: String) { 34 | let formatter = DateFormatter() 35 | formatter.locale = Locale(identifier: "en_US_POSIX") 36 | formatter.dateFormat = formatString 37 | 38 | super.init(dateFormatter: formatter) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Source/Transformable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Transformable.swift 3 | // HandyJSON 4 | // 5 | // Created by zhouzhuo on 15/07/2017. 6 | // Copyright © 2017 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol _Transformable: _Measurable {} 12 | 13 | extension _Transformable { 14 | 15 | static func transform(from object: Any) -> Self? { 16 | if let typedObject = object as? Self { 17 | return typedObject 18 | } 19 | switch self { 20 | case let type as _ExtendCustomBasicType.Type: 21 | return type._transform(from: object) as? Self 22 | case let type as _BuiltInBridgeType.Type: 23 | return type._transform(from: object) as? Self 24 | case let type as _BuiltInBasicType.Type: 25 | return type._transform(from: object) as? Self 26 | case let type as _RawEnumProtocol.Type: 27 | return type._transform(from: object) as? Self 28 | case let type as _ExtendCustomModelType.Type: 29 | return type._transform(from: object) as? Self 30 | default: 31 | return nil 32 | } 33 | } 34 | 35 | func plainValue() -> Any? { 36 | switch self { 37 | case let rawValue as _ExtendCustomBasicType: 38 | return rawValue._plainValue() 39 | case let rawValue as _BuiltInBridgeType: 40 | return rawValue._plainValue() 41 | case let rawValue as _BuiltInBasicType: 42 | return rawValue._plainValue() 43 | case let rawValue as _RawEnumProtocol: 44 | return rawValue._plainValue() 45 | case let rawValue as _ExtendCustomModelType: 46 | return rawValue._plainValue() 47 | default: 48 | return nil 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Tests/HandyJSONTests/GenericTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericTypes.swift 3 | // HandyJSON 4 | // 5 | // Created by chantu on 2019/3/27. 6 | // Copyright © 2019 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import HandyJSON 11 | 12 | struct GenericStruct: HandyJSON { 13 | var t: T? 14 | } 15 | 16 | class BaseGenericClass: HandyJSON { 17 | var base: T? 18 | required init() {} 19 | } 20 | 21 | class SuperGenericClassInheritGeneric: BaseGenericClass { 22 | var str: String? 23 | var t: T? 24 | 25 | required init() {} 26 | } 27 | 28 | class SuperGenericClass: BasicTypesInClass { 29 | var str: String? 30 | var t: T? 31 | 32 | required init() {} 33 | } 34 | 35 | class SubGenericClassInheritGeneric: SuperGenericClassInheritGeneric { 36 | var sub: T? 37 | 38 | required init() {} 39 | } 40 | 41 | class SubGenericClass: InheritanceBasicType { 42 | var sub: T? 43 | 44 | required init() {} 45 | } 46 | 47 | class GenericWithNormalClass: HandyJSON { 48 | var t: T? 49 | 50 | required init() {} 51 | } 52 | 53 | class SubGenericClassInheritGenericWithNormalClass: SuperGenericClassInheritGeneric { 54 | var sub: T? 55 | 56 | required init() {} 57 | } 58 | 59 | class ComplicatedGenericClass: SubGenericClassInheritGeneric { 60 | var handyJSONProtocol1: V? 61 | var handyJSON: HandyJSON! 62 | var basicTypesInClass: T? 63 | var inheritanceBasicType: U? 64 | var basicTypesInStruct: BasicTypesInStruct? 65 | var handyJSONProtocol2: W? 66 | 67 | required init() {} 68 | } 69 | -------------------------------------------------------------------------------- /Source/EnumTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnumTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Kaan Dedeoglu on 3/20/15. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class EnumTransform: TransformType { 32 | public typealias Object = T 33 | public typealias JSON = T.RawValue 34 | 35 | public init() {} 36 | 37 | open func transformFromJSON(_ value: Any?) -> T? { 38 | if let raw = value as? T.RawValue { 39 | return T(rawValue: raw) 40 | } 41 | return nil 42 | } 43 | 44 | open func transformToJSON(_ value: T?) -> T.RawValue? { 45 | if let obj = value { 46 | return obj.rawValue 47 | } 48 | return nil 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Source/DataTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Yagrushkin, Evgeny on 8/30/16. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class DataTransform: TransformType { 32 | public typealias Object = Data 33 | public typealias JSON = String 34 | 35 | public init() {} 36 | 37 | open func transformFromJSON(_ value: Any?) -> Data? { 38 | guard let string = value as? String else{ 39 | return nil 40 | } 41 | return Data(base64Encoded: string) 42 | } 43 | 44 | open func transformToJSON(_ value: Data?) -> String? { 45 | guard let data = value else{ 46 | return nil 47 | } 48 | return data.base64EncodedString() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.2 3 | 4 | env: 5 | global: 6 | - IOS_FRAMEWORK_SCHEME="HandyJSON iOS" 7 | - OSX_FRAMEWORK_SCHEME="HandyJSON Mac" 8 | - TVOS_FRAMEWORK_SCHEME="HandyJSON tvOS" 9 | - WATCHOS_FRAMEWORK_SCHEME="HandyJSON watchOS" 10 | - IOS_SDK=iphonesimulator12.2 11 | - OSX_SDK=macosx10.14 12 | 13 | before_install: 14 | - gem install xcpretty --no-document --quiet 15 | 16 | script: 17 | - set -o pipefail 18 | - xcodebuild -version 19 | - xcodebuild -showsdks 20 | - echo "Applying fix for rdar://49326587 see https://developer.apple.com/documentation/xcode_release_notes/xcode_10_2_1_release_notes?language=objc" 21 | - sudo mkdir '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 9.3.simruntime/Contents/Resources/RuntimeRoot/usr/lib/swift' 22 | 23 | - xcodebuild -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" -destination "platform=iOS Simulator,OS=12.0,name=iPhone XS Max" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c 24 | 25 | - xcodebuild -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" -destination "platform=iOS Simulator,OS=11.4,name=iPhone 8" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c 26 | 27 | - xcodebuild -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" -destination "platform=iOS Simulator,OS=10.1,name=iPhone 7 Plus" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c 28 | 29 | - xcodebuild -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" -destination "platform=iOS Simulator,OS=9.3,name=iPhone 6" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c 30 | 31 | - xcodebuild -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" -destination "platform=iOS Simulator,OS=8.1,name=iPhone 5" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c 32 | 33 | - xcodebuild -scheme "HandyJSON macOS" test 34 | 35 | - xcodebuild -scheme "HandyJSON tvOS" -destination "platform=tvOS Simulator,OS=12.0,name=Apple TV" test 36 | 37 | after_success: 38 | - bash <(curl -s https://codecov.io/bash) 39 | -------------------------------------------------------------------------------- /Source/TransformOf.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformOf.swift 3 | // ObjectMapper 4 | // 5 | // Created by Syo Ikeda on 1/23/15. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | open class TransformOf: TransformType { 30 | public typealias Object = ObjectType 31 | public typealias JSON = JSONType 32 | 33 | private let fromJSON: (JSONType?) -> ObjectType? 34 | private let toJSON: (ObjectType?) -> JSONType? 35 | 36 | public init(fromJSON: @escaping(JSONType?) -> ObjectType?, toJSON: @escaping(ObjectType?) -> JSONType?) { 37 | self.fromJSON = fromJSON 38 | self.toJSON = toJSON 39 | } 40 | 41 | open func transformFromJSON(_ value: Any?) -> ObjectType? { 42 | return fromJSON(value as? JSONType) 43 | } 44 | 45 | open func transformToJSON(_ value: ObjectType?) -> JSONType? { 46 | return toJSON(value) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Source/DateTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 2014-10-13. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class DateTransform: TransformType { 32 | public typealias Object = Date 33 | public typealias JSON = Double 34 | 35 | public init() {} 36 | 37 | open func transformFromJSON(_ value: Any?) -> Date? { 38 | if let timeInt = value as? Double { 39 | return Date(timeIntervalSince1970: TimeInterval(timeInt)) 40 | } 41 | 42 | if let timeStr = value as? String { 43 | return Date(timeIntervalSince1970: TimeInterval(atof(timeStr))) 44 | } 45 | 46 | return nil 47 | } 48 | 49 | open func transformToJSON(_ value: Date?) -> Double? { 50 | if let date = value { 51 | return Double(date.timeIntervalSince1970) 52 | } 53 | return nil 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Source/DateFormatterTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormatterTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 2015-03-09. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class DateFormatterTransform: TransformType { 32 | public typealias Object = Date 33 | public typealias JSON = String 34 | 35 | public let dateFormatter: DateFormatter 36 | 37 | public init(dateFormatter: DateFormatter) { 38 | self.dateFormatter = dateFormatter 39 | } 40 | 41 | open func transformFromJSON(_ value: Any?) -> Date? { 42 | if let dateString = value as? String { 43 | return dateFormatter.date(from: dateString) 44 | } 45 | return nil 46 | } 47 | 48 | open func transformToJSON(_ value: Date?) -> String? { 49 | if let date = value { 50 | return dateFormatter.string(from: date) 51 | } 52 | return nil 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Source/NSDecimalNumberTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformOf.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 8/22/16. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class NSDecimalNumberTransform: TransformType { 32 | public typealias Object = NSDecimalNumber 33 | public typealias JSON = String 34 | 35 | public init() {} 36 | 37 | open func transformFromJSON(_ value: Any?) -> NSDecimalNumber? { 38 | if let string = value as? String { 39 | return NSDecimalNumber(string: string) 40 | } 41 | if let double = value as? Double { 42 | return NSDecimalNumber(value: double) 43 | } 44 | return nil 45 | } 46 | 47 | open func transformToJSON(_ value: NSDecimalNumber?) -> String? { 48 | guard let value = value else { return nil } 49 | return value.description 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Tests/HandyJSONTests/NestTypes.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 | // NestTypes.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 07/09/2017. 22 | // 23 | 24 | import Foundation 25 | import HandyJSON 26 | 27 | enum StringEnum: String, HandyJSONEnum { 28 | case Default = "Default" 29 | case Another = "Another" 30 | } 31 | 32 | class LowerLayerModel: NSObject, HandyJSON { 33 | var enumMember: StringEnum = StringEnum.Default 34 | var enumMemberOptional: StringEnum? 35 | var enumMemberImplicitlyUnwrapped: StringEnum! 36 | var structMember: BasicTypesInStruct = BasicTypesInStruct() 37 | var structMemberOptional: BasicTypesInStruct? 38 | var structMemberImplicitlyUnwrapped: BasicTypesInStruct! 39 | var classMember: BasicTypesInClass = BasicTypesInClass() 40 | var classMemberOptional: BasicTypesInClass? 41 | var classMemberImplicitlyUnwrapped: BasicTypesInClass! 42 | 43 | required override init() {} 44 | } 45 | 46 | class TopMostLayerModel: HandyJSON { 47 | var structMember: BasicTypesInStruct = BasicTypesInStruct() 48 | var structMemberOptional: BasicTypesInStruct? 49 | var structMemberImplicitlyUnwrapped: BasicTypesInStruct! 50 | var classMember: BasicTypesInClass = BasicTypesInClass() 51 | var classMemberOptional: BasicTypesInClass? 52 | var classMemberImplicitlyUnwrapped: BasicTypesInClass! 53 | var lowerLayerModel: LowerLayerModel = LowerLayerModel() 54 | var lowerLayerModelOptional: LowerLayerModel? 55 | var lowerLayerModelImplicitlyUnwrapped: LowerLayerModel! 56 | 57 | required init() {} 58 | } 59 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Source/BuiltInBridgeType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BuiltInBridgeType.swift 3 | // HandyJSON 4 | // 5 | // Created by zhouzhuo on 15/07/2017. 6 | // Copyright © 2017 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol _BuiltInBridgeType: _Transformable { 12 | 13 | static func _transform(from object: Any) -> _BuiltInBridgeType? 14 | func _plainValue() -> Any? 15 | } 16 | 17 | extension NSString: _BuiltInBridgeType { 18 | 19 | static func _transform(from object: Any) -> _BuiltInBridgeType? { 20 | if let str = String.transform(from: object) { 21 | return NSString(string: str) 22 | } 23 | return nil 24 | } 25 | 26 | func _plainValue() -> Any? { 27 | return self 28 | } 29 | } 30 | 31 | extension NSNumber: _BuiltInBridgeType { 32 | 33 | static func _transform(from object: Any) -> _BuiltInBridgeType? { 34 | switch object { 35 | case let num as NSNumber: 36 | return num 37 | case let str as NSString: 38 | let lowercase = str.lowercased 39 | if lowercase == "true" { 40 | return NSNumber(booleanLiteral: true) 41 | } else if lowercase == "false" { 42 | return NSNumber(booleanLiteral: false) 43 | } else { 44 | // normal number 45 | let formatter = NumberFormatter() 46 | formatter.numberStyle = .decimal 47 | return formatter.number(from: str as String) 48 | } 49 | default: 50 | return nil 51 | } 52 | } 53 | 54 | func _plainValue() -> Any? { 55 | return self 56 | } 57 | } 58 | 59 | extension NSArray: _BuiltInBridgeType { 60 | 61 | static func _transform(from object: Any) -> _BuiltInBridgeType? { 62 | return object as? NSArray 63 | } 64 | 65 | func _plainValue() -> Any? { 66 | return (self as? Array)?.plainValue() 67 | } 68 | } 69 | 70 | extension NSDictionary: _BuiltInBridgeType { 71 | 72 | static func _transform(from object: Any) -> _BuiltInBridgeType? { 73 | return object as? NSDictionary 74 | } 75 | 76 | func _plainValue() -> Any? { 77 | return (self as? Dictionary)?.plainValue() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Tests/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 = A.deserialize(from: jsonString) 46 | XCTAssertNil(a) 47 | let b = [A].deserialize(from: 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 = B.deserialize(from: jsonString, designatedPath: "wrong") 62 | XCTAssertNil(a) 63 | let b = B.deserialize(from: jsonString, designatedPath: "name.name") 64 | XCTAssertNil(b) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Source/OtherExtension.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 | // OtherExtension.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 08/01/2017. 22 | // 23 | 24 | protocol UTF8Initializable { 25 | init?(validatingUTF8: UnsafePointer) 26 | } 27 | 28 | extension String : UTF8Initializable {} 29 | 30 | extension Array where Element : UTF8Initializable { 31 | 32 | init(utf8Strings: UnsafePointer) { 33 | var strings = [Element]() 34 | var pointer = utf8Strings 35 | while let string = Element(validatingUTF8: pointer) { 36 | strings.append(string) 37 | while pointer.pointee != 0 { 38 | pointer.advance() 39 | } 40 | pointer.advance() 41 | guard pointer.pointee != 0 else { 42 | break 43 | } 44 | } 45 | self = strings 46 | } 47 | } 48 | 49 | extension Strideable { 50 | mutating func advance() { 51 | self = advanced(by: 1) 52 | } 53 | } 54 | 55 | extension UnsafePointer { 56 | 57 | init(_ pointer: UnsafePointer) { 58 | self = UnsafeRawPointer(pointer).assumingMemoryBound(to: Pointee.self) 59 | } 60 | } 61 | 62 | func relativePointer(base: UnsafePointer, offset: U) -> UnsafePointer where U : FixedWidthInteger { 63 | return UnsafeRawPointer(base).advanced(by: Int(integer: offset)).assumingMemoryBound(to: V.self) 64 | } 65 | 66 | extension Int { 67 | fileprivate init(integer: T) { 68 | switch integer { 69 | case let value as Int: self = value 70 | case let value as Int32: self = Int(value) 71 | case let value as Int16: self = Int(value) 72 | case let value as Int8: self = Int(value) 73 | default: self = 0 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Source/URLTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Tristan Himmelman on 2014-10-27. 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2014-2016 Hearst 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | import Foundation 30 | 31 | open class URLTransform: TransformType { 32 | public typealias Object = URL 33 | public typealias JSON = String 34 | private let shouldEncodeURLString: Bool 35 | 36 | /** 37 | Initializes the URLTransform with an option to encode URL strings before converting them to an NSURL 38 | - parameter shouldEncodeUrlString: when true (the default) the string is encoded before passing 39 | to `NSURL(string:)` 40 | - returns: an initialized transformer 41 | */ 42 | public init(shouldEncodeURLString: Bool = true) { 43 | self.shouldEncodeURLString = shouldEncodeURLString 44 | } 45 | 46 | open func transformFromJSON(_ value: Any?) -> URL? { 47 | guard let URLString = value as? String else { return nil } 48 | 49 | if !shouldEncodeURLString { 50 | return URL(string: URLString) 51 | } 52 | 53 | guard let escapedURLString = URLString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else { 54 | return nil 55 | } 56 | return URL(string: escapedURLString) 57 | } 58 | 59 | open func transformToJSON(_ value: URL?) -> String? { 60 | if let URL = value { 61 | return URL.absoluteString 62 | } 63 | return nil 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Source/Properties.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 | // Created by zhouzhuo on 07/01/2017. 19 | // 20 | 21 | 22 | /// An instance property 23 | struct Property { 24 | let key: String 25 | let value: Any 26 | 27 | /// An instance property description 28 | struct Description { 29 | public let key: String 30 | public let type: Any.Type 31 | public let offset: Int 32 | public func write(_ value: Any, to storage: UnsafeMutableRawPointer) { 33 | return extensions(of: type).write(value, to: storage.advanced(by: offset)) 34 | } 35 | } 36 | } 37 | 38 | /// Retrieve properties for `instance` 39 | func getProperties(forInstance instance: Any) -> [Property]? { 40 | if let props = getProperties(forType: type(of: instance)) { 41 | var copy = extensions(of: instance) 42 | let storage = copy.storage() 43 | return props.map { 44 | nextProperty(description: $0, storage: storage) 45 | } 46 | } 47 | return nil 48 | } 49 | 50 | private func nextProperty(description: Property.Description, storage: UnsafeRawPointer) -> Property { 51 | return Property( 52 | key: description.key, 53 | value: extensions(of: description.type).value(from: storage.advanced(by: description.offset)) 54 | ) 55 | } 56 | 57 | /// Retrieve property descriptions for `type` 58 | func getProperties(forType type: Any.Type) -> [Property.Description]? { 59 | if let structDescriptor = Metadata.Struct(anyType: type) { 60 | return structDescriptor.propertyDescriptions() 61 | } else if let classDescriptor = Metadata.Class(anyType: type) { 62 | return classDescriptor.propertyDescriptions() 63 | } else if let objcClassDescriptor = Metadata.ObjcClassWrapper(anyType: type), 64 | let targetType = objcClassDescriptor.targetType { 65 | return getProperties(forType: targetType) 66 | } 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /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 | 15 | 1. reflection 16 | 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 2016 Brad Hilton 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | 2. ObjectMapper 40 | 41 | The MIT License (MIT) 42 | Copyright (c) 2014 Hearst 43 | 44 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 49 | -------------------------------------------------------------------------------- /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 Biz: String, HandyJSONEnum { 23 | case social = "social" 24 | case education = "education" 25 | case news = "news" 26 | } 27 | 28 | protocol Data: HandyJSON {} 29 | 30 | class Common: Data { 31 | var id: Int? 32 | required init() {} 33 | } 34 | 35 | class Model: Common { 36 | var aInt: Int? 37 | var aStr: String? 38 | required init() {} 39 | } 40 | 41 | class Result: HandyJSON { 42 | var code: Int? 43 | var biz: Biz? 44 | var data: T? 45 | required init() {} 46 | } 47 | 48 | class ViewController: UIViewController { 49 | 50 | override func viewDidLoad() { 51 | super.viewDidLoad() 52 | // Do any additional setup after loading the view, typically from a nib. 53 | 54 | print("\n--------------------- serilization ---------------------\n") 55 | self.serialization() 56 | print("\n--------------------- deserilization ---------------------\n") 57 | self.deserialization() 58 | } 59 | 60 | override func didReceiveMemoryWarning() { 61 | super.didReceiveMemoryWarning() 62 | // Dispose of any resources that can be recreated. 63 | } 64 | 65 | func serialization() { 66 | let model = Model() 67 | model.id = 1 68 | model.aInt = 100 69 | model.aStr = "string data" 70 | let result = Result() 71 | result.biz = Biz.social 72 | result.code = 200 73 | result.data = model 74 | print(result.toJSON()!) 75 | print(result.toJSONString()!) 76 | print(result.toJSONString(prettyPrint: true)!) 77 | 78 | print([result].toJSON()) 79 | print([result].toJSONString()!) 80 | print([result].toJSONString(prettyPrint: true)!) 81 | } 82 | 83 | func deserialization() { 84 | let jsonString = "{\"data\":{\"aInt\":100,\"aStr\":\"string data\",\"id\":1},\"code\":200,\"biz\":\"social\"}" 85 | if let result = Result.deserialize(from: jsonString) { 86 | print(result.data?.id ?? 0) 87 | print(result.code ?? "") 88 | print(result.biz ?? "") 89 | print(result.data?.aInt ?? 0) 90 | print(result.data?.aStr ?? "") 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Source/FieldDescriptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldDescriptor.swift 3 | // HandyJSON 4 | // 5 | // Created by chantu on 2019/1/31. 6 | // Copyright © 2019 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum FieldDescriptorKind : UInt16 { 12 | // Swift nominal types. 13 | case Struct = 0 14 | case Class 15 | case Enum 16 | 17 | // Fixed-size multi-payload enums have a special descriptor format that 18 | // encodes spare bits. 19 | // 20 | // FIXME: Actually implement this. For now, a descriptor with this kind 21 | // just means we also have a builtin descriptor from which we get the 22 | // size and alignment. 23 | case MultiPayloadEnum 24 | 25 | // A Swift opaque protocol. There are no fields, just a record for the 26 | // type itself. 27 | case `Protocol` 28 | 29 | // A Swift class-bound protocol. 30 | case ClassProtocol 31 | 32 | // An Objective-C protocol, which may be imported or defined in Swift. 33 | case ObjCProtocol 34 | 35 | // An Objective-C class, which may be imported or defined in Swift. 36 | // In the former case, field type metadata is not emitted, and 37 | // must be obtained from the Objective-C runtime. 38 | case ObjCClass 39 | } 40 | 41 | struct FieldDescriptor: PointerType { 42 | 43 | var pointer: UnsafePointer<_FieldDescriptor> 44 | 45 | var fieldRecordSize: Int { 46 | return Int(pointer.pointee.fieldRecordSize) 47 | } 48 | 49 | var numFields: Int { 50 | return Int(pointer.pointee.numFields) 51 | } 52 | 53 | var fieldRecords: [FieldRecord] { 54 | return (0.. FieldRecord in 55 | return FieldRecord(pointer: UnsafePointer<_FieldRecord>(pointer + 1) + i) 56 | }) 57 | } 58 | } 59 | 60 | struct _FieldDescriptor { 61 | var mangledTypeNameOffset: Int32 62 | var superClassOffset: Int32 63 | var fieldDescriptorKind: FieldDescriptorKind 64 | var fieldRecordSize: Int16 65 | var numFields: Int32 66 | } 67 | 68 | struct FieldRecord: PointerType { 69 | 70 | var pointer: UnsafePointer<_FieldRecord> 71 | 72 | var fieldRecordFlags: Int { 73 | return Int(pointer.pointee.fieldRecordFlags) 74 | } 75 | 76 | var mangledTypeName: UnsafePointer? { 77 | let address = Int(bitPattern: pointer) + 1 * 4 78 | let offset = Int(pointer.pointee.mangledTypeNameOffset) 79 | let cString = UnsafePointer(bitPattern: address + offset) 80 | return cString 81 | } 82 | 83 | var fieldName: String { 84 | let address = Int(bitPattern: pointer) + 2 * 4 85 | let offset = Int(pointer.pointee.fieldNameOffset) 86 | if let cString = UnsafePointer(bitPattern: address + offset) { 87 | return String(cString: cString) 88 | } 89 | return "" 90 | } 91 | } 92 | 93 | struct _FieldRecord { 94 | var fieldRecordFlags: Int32 95 | var mangledTypeNameOffset: Int32 96 | var fieldNameOffset: Int32 97 | } 98 | -------------------------------------------------------------------------------- /Source/Serializer.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 | import Foundation 25 | 26 | public extension HandyJSON { 27 | 28 | func toJSON() -> [String: Any]? { 29 | if let dict = Self._serializeAny(object: self) as? [String: Any] { 30 | return dict 31 | } 32 | return nil 33 | } 34 | 35 | func toJSONString(prettyPrint: Bool = false) -> String? { 36 | 37 | if let anyObject = self.toJSON() { 38 | if JSONSerialization.isValidJSONObject(anyObject) { 39 | do { 40 | let jsonData: Data 41 | if prettyPrint { 42 | jsonData = try JSONSerialization.data(withJSONObject: anyObject, options: [.prettyPrinted]) 43 | } else { 44 | jsonData = try JSONSerialization.data(withJSONObject: anyObject, options: []) 45 | } 46 | return String(data: jsonData, encoding: .utf8) 47 | } catch let error { 48 | InternalLogger.logError(error) 49 | } 50 | } else { 51 | InternalLogger.logDebug("\(anyObject)) is not a valid JSON Object") 52 | } 53 | } 54 | return nil 55 | } 56 | } 57 | 58 | public extension Collection where Iterator.Element: HandyJSON { 59 | 60 | func toJSON() -> [[String: Any]?] { 61 | return self.map{ $0.toJSON() } 62 | } 63 | 64 | func toJSONString(prettyPrint: Bool = false) -> String? { 65 | 66 | let anyArray = self.toJSON() 67 | if JSONSerialization.isValidJSONObject(anyArray) { 68 | do { 69 | let jsonData: Data 70 | if prettyPrint { 71 | jsonData = try JSONSerialization.data(withJSONObject: anyArray, options: [.prettyPrinted]) 72 | } else { 73 | jsonData = try JSONSerialization.data(withJSONObject: anyArray, options: []) 74 | } 75 | return String(data: jsonData, encoding: .utf8) 76 | } catch let error { 77 | InternalLogger.logError(error) 78 | } 79 | } else { 80 | InternalLogger.logDebug("\(self.toJSON()) is not a valid JSON Object") 81 | } 82 | return nil 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Source/AnyExtensions.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 | // AnyExtension.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 08/01/2017. 22 | // 23 | 24 | protocol AnyExtensions {} 25 | 26 | extension AnyExtensions { 27 | 28 | public static func isValueTypeOrSubtype(_ value: Any) -> Bool { 29 | return value is Self 30 | } 31 | 32 | public static func value(from storage: UnsafeRawPointer) -> Any { 33 | return storage.assumingMemoryBound(to: self).pointee 34 | } 35 | 36 | public static func write(_ value: Any, to storage: UnsafeMutableRawPointer) { 37 | guard let this = value as? Self else { 38 | return 39 | } 40 | storage.assumingMemoryBound(to: self).pointee = this 41 | } 42 | 43 | public static func takeValue(from anyValue: Any) -> Self? { 44 | return anyValue as? Self 45 | } 46 | } 47 | 48 | func extensions(of type: Any.Type) -> AnyExtensions.Type { 49 | struct Extensions : AnyExtensions {} 50 | var extensions: AnyExtensions.Type = Extensions.self 51 | withUnsafePointer(to: &extensions) { pointer in 52 | UnsafeMutableRawPointer(mutating: pointer).assumingMemoryBound(to: Any.Type.self).pointee = type 53 | } 54 | return extensions 55 | } 56 | 57 | func extensions(of value: Any) -> AnyExtensions { 58 | struct Extensions : AnyExtensions {} 59 | var extensions: AnyExtensions = Extensions() 60 | withUnsafePointer(to: &extensions) { pointer in 61 | UnsafeMutableRawPointer(mutating: pointer).assumingMemoryBound(to: Any.self).pointee = value 62 | } 63 | return extensions 64 | } 65 | 66 | /// Tests if `value` is `type` or a subclass of `type` 67 | func value(_ value: Any, is type: Any.Type) -> Bool { 68 | return extensions(of: type).isValueTypeOrSubtype(value) 69 | } 70 | 71 | /// Tests equality of any two existential types 72 | func == (lhs: Any.Type, rhs: Any.Type) -> Bool { 73 | return Metadata(type: lhs) == Metadata(type: rhs) 74 | } 75 | 76 | // MARK: AnyExtension + Storage 77 | extension AnyExtensions { 78 | 79 | mutating func storage() -> UnsafeRawPointer { 80 | if type(of: self) is AnyClass { 81 | let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque() 82 | return UnsafeRawPointer(opaquePointer) 83 | } else { 84 | return withUnsafePointer(to: &self) { pointer in 85 | return UnsafeRawPointer(pointer) 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /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: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 29 | // Override point for customization after application launch. 30 | let rootViewController = ViewController(nibName: nil, bundle: nil) 31 | self.window = UIWindow(frame: UIScreen.main.bounds) 32 | self.window?.backgroundColor = UIColor.white 33 | self.window?.rootViewController = rootViewController 34 | self.window?.makeKeyAndVisible() 35 | return true 36 | } 37 | 38 | func applicationWillResignActive(_ application: UIApplication) { 39 | // 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. 40 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 41 | } 42 | 43 | func applicationDidEnterBackground(_ application: UIApplication) { 44 | // 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. 45 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 46 | } 47 | 48 | func applicationWillEnterForeground(_ application: UIApplication) { 49 | // 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. 50 | } 51 | 52 | func applicationDidBecomeActive(_ application: UIApplication) { 53 | // 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. 54 | } 55 | 56 | func applicationWillTerminate(_ application: UIApplication) { 57 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 58 | } 59 | 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Source/Measuable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Measuable.swift 3 | // HandyJSON 4 | // 5 | // Created by zhouzhuo on 15/07/2017. 6 | // Copyright © 2017 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias Byte = Int8 12 | 13 | public protocol _Measurable {} 14 | 15 | extension _Measurable { 16 | 17 | // locate the head of a struct type object in memory 18 | mutating func headPointerOfStruct() -> UnsafeMutablePointer { 19 | 20 | return withUnsafeMutablePointer(to: &self) { 21 | return UnsafeMutableRawPointer($0).bindMemory(to: Byte.self, capacity: MemoryLayout.stride) 22 | } 23 | } 24 | 25 | // locating the head of a class type object in memory 26 | mutating func headPointerOfClass() -> UnsafeMutablePointer { 27 | 28 | let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque() 29 | let mutableTypedPointer = opaquePointer.bindMemory(to: Byte.self, capacity: MemoryLayout.stride) 30 | return UnsafeMutablePointer(mutableTypedPointer) 31 | } 32 | 33 | // locating the head of an object 34 | mutating func headPointer() -> UnsafeMutablePointer { 35 | if Self.self is AnyClass { 36 | return self.headPointerOfClass() 37 | } else { 38 | return self.headPointerOfStruct() 39 | } 40 | } 41 | 42 | func isNSObjectType() -> Bool { 43 | return (type(of: self) as? NSObject.Type) != nil 44 | } 45 | 46 | func getBridgedPropertyList() -> Set { 47 | if let anyClass = type(of: self) as? AnyClass { 48 | return _getBridgedPropertyList(anyClass: anyClass) 49 | } 50 | return [] 51 | } 52 | 53 | func _getBridgedPropertyList(anyClass: AnyClass) -> Set { 54 | if !(anyClass is HandyJSON.Type) { 55 | return [] 56 | } 57 | var propertyList = Set() 58 | if let superClass = class_getSuperclass(anyClass), superClass != NSObject.self { 59 | propertyList = propertyList.union(_getBridgedPropertyList(anyClass: superClass)) 60 | } 61 | let count = UnsafeMutablePointer.allocate(capacity: 1) 62 | if let props = class_copyPropertyList(anyClass, count) { 63 | for i in 0 ..< count.pointee { 64 | let name = String(cString: property_getName(props.advanced(by: Int(i)).pointee)) 65 | propertyList.insert(name) 66 | } 67 | free(props) 68 | } 69 | #if swift(>=4.1) 70 | count.deallocate() 71 | #else 72 | count.deallocate(capacity: 1) 73 | #endif 74 | return propertyList 75 | } 76 | 77 | // memory size occupy by self object 78 | static func size() -> Int { 79 | return MemoryLayout.size 80 | } 81 | 82 | // align 83 | static func align() -> Int { 84 | return MemoryLayout.alignment 85 | } 86 | 87 | // Returns the offset to the next integer that is greater than 88 | // or equal to Value and is a multiple of Align. Align must be 89 | // non-zero. 90 | static func offsetToAlignment(value: Int, align: Int) -> Int { 91 | let m = value % align 92 | return m == 0 ? 0 : (align - m) 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /Tests/HandyJSONTests/TestUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestUtils.swift 3 | // HandyJSON 4 | // 5 | // Created by zhouzhuo on 10/12/2016. 6 | // Copyright © 2016 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | 12 | func stringCompareHelper(_ actual: String?, _ expected: String?) { 13 | // print(actual ?? "") 14 | // print(expected ?? "") 15 | XCTAssertTrue(expected == actual, "expected value:\(expected ?? "nil") not equal to actual:\(actual ?? "nil")") 16 | } 17 | 18 | fileprivate func toJSONObject(_ string: String?) -> NSObject? { 19 | guard let _string = string else { 20 | return nil 21 | } 22 | if let rawData = (_string as NSString).data(using: String.Encoding.utf8.rawValue) { 23 | if let jsonObject = try? JSONSerialization.jsonObject(with: rawData, options: []) as? NSObject { 24 | return jsonObject 25 | } 26 | } 27 | return nil 28 | } 29 | 30 | fileprivate func compareJSONObject(_ left: NSObject?, _ right: NSObject?) -> Bool { 31 | if left == nil { 32 | return right == nil 33 | } 34 | if let leftDict = left as? NSDictionary { 35 | if let rightDict = right as? NSDictionary { 36 | for key in leftDict.allKeys { 37 | if !compareJSONObject(leftDict[key] as? NSObject, rightDict[key] as? NSObject) { 38 | return false 39 | } 40 | } 41 | for key in rightDict.allKeys { 42 | if !compareJSONObject(leftDict[key] as? NSObject, rightDict[key] as? NSObject) { 43 | return false 44 | } 45 | } 46 | return true 47 | } 48 | } else if let leftArray = left as? NSArray { 49 | if let rightArray = right as? NSArray { 50 | for u in leftArray { 51 | var found = false 52 | for v in rightArray { 53 | if compareJSONObject(u as? NSObject, v as? NSObject) { 54 | found = true 55 | } 56 | } 57 | if !found { 58 | return false 59 | } 60 | } 61 | for v in rightArray { 62 | var found = false 63 | for u in leftArray { 64 | if compareJSONObject(u as? NSObject, v as? NSObject) { 65 | found = true 66 | } 67 | } 68 | if !found { 69 | return false 70 | } 71 | } 72 | return true 73 | } 74 | } else if let leftString = left as? NSString { 75 | if let rightString = right as? NSString { 76 | return leftString == rightString 77 | } 78 | } else if let leftNumber = left as? NSNumber { 79 | if let rightNumber = right as? NSNumber { 80 | return leftNumber == rightNumber 81 | } 82 | } else if let _ = left as? NSNull, let _ = right as? NSNull { 83 | return true 84 | } 85 | return false 86 | } 87 | 88 | func jsonStringCompareHelper(_ actual: String?, _ expected: String?) { 89 | let left = toJSONObject(actual) 90 | let right = toJSONObject(expected) 91 | // print("\(left)") 92 | // print("\(right)") 93 | XCTAssertTrue(compareJSONObject(left, right)) 94 | } 95 | -------------------------------------------------------------------------------- /Source/HexColorTransform.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HexColorTransform.swift 3 | // ObjectMapper 4 | // 5 | // Created by Vitaliy Kuzmenko on 10/10/16. 6 | // Copyright © 2016 hearst. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) || os(watchOS) 10 | import UIKit 11 | #else 12 | import Cocoa 13 | #endif 14 | 15 | open class HexColorTransform: TransformType { 16 | 17 | #if os(iOS) || os(tvOS) || os(watchOS) 18 | public typealias Object = UIColor 19 | #else 20 | public typealias Object = NSColor 21 | #endif 22 | 23 | public typealias JSON = String 24 | 25 | var prefix: Bool = false 26 | 27 | var alpha: Bool = false 28 | 29 | public init(prefixToJSON: Bool = false, alphaToJSON: Bool = false) { 30 | alpha = alphaToJSON 31 | prefix = prefixToJSON 32 | } 33 | 34 | open func transformFromJSON(_ value: Any?) -> Object? { 35 | if let rgba = value as? String { 36 | if rgba.hasPrefix("#") { 37 | let index = rgba.index(rgba.startIndex, offsetBy: 1) 38 | let hex = String(rgba[index...]) 39 | return getColor(hex: hex) 40 | } else { 41 | return getColor(hex: rgba) 42 | } 43 | } 44 | return nil 45 | } 46 | 47 | open func transformToJSON(_ value: Object?) -> JSON? { 48 | if let value = value { 49 | return hexString(color: value) 50 | } 51 | return nil 52 | } 53 | 54 | fileprivate func hexString(color: Object) -> String { 55 | let comps = color.cgColor.components! 56 | let r = Int(comps[0] * 255) 57 | let g = Int(comps[1] * 255) 58 | let b = Int(comps[2] * 255) 59 | let a = Int(comps[3] * 255) 60 | var hexString: String = "" 61 | if prefix { 62 | hexString = "#" 63 | } 64 | hexString += String(format: "%02X%02X%02X", r, g, b) 65 | 66 | if alpha { 67 | hexString += String(format: "%02X", a) 68 | } 69 | return hexString 70 | } 71 | 72 | fileprivate func getColor(hex: String) -> Object? { 73 | var red: CGFloat = 0.0 74 | var green: CGFloat = 0.0 75 | var blue: CGFloat = 0.0 76 | var alpha: CGFloat = 1.0 77 | 78 | let scanner = Scanner(string: hex) 79 | var hexValue: CUnsignedLongLong = 0 80 | if scanner.scanHexInt64(&hexValue) { 81 | switch (hex.count) { 82 | case 3: 83 | red = CGFloat((hexValue & 0xF00) >> 8) / 15.0 84 | green = CGFloat((hexValue & 0x0F0) >> 4) / 15.0 85 | blue = CGFloat(hexValue & 0x00F) / 15.0 86 | case 4: 87 | red = CGFloat((hexValue & 0xF000) >> 12) / 15.0 88 | green = CGFloat((hexValue & 0x0F00) >> 8) / 15.0 89 | blue = CGFloat((hexValue & 0x00F0) >> 4) / 15.0 90 | alpha = CGFloat(hexValue & 0x000F) / 15.0 91 | case 6: 92 | red = CGFloat((hexValue & 0xFF0000) >> 16) / 255.0 93 | green = CGFloat((hexValue & 0x00FF00) >> 8) / 255.0 94 | blue = CGFloat(hexValue & 0x0000FF) / 255.0 95 | case 8: 96 | red = CGFloat((hexValue & 0xFF000000) >> 24) / 255.0 97 | green = CGFloat((hexValue & 0x00FF0000) >> 16) / 255.0 98 | blue = CGFloat((hexValue & 0x0000FF00) >> 8) / 255.0 99 | alpha = CGFloat(hexValue & 0x000000FF) / 255.0 100 | default: 101 | // Invalid RGB string, number of characters after '#' should be either 3, 4, 6 or 8 102 | return nil 103 | } 104 | } else { 105 | // "Scan hex error 106 | return nil 107 | } 108 | #if os(iOS) || os(tvOS) || os(watchOS) 109 | return UIColor(red: red, green: green, blue: blue, alpha: alpha) 110 | #else 111 | return NSColor(calibratedRed: red, green: green, blue: blue, alpha: alpha) 112 | #endif 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Tests/HandyJSONTests/BasicTypesInStruct.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 | // BasicTypesInStruct.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 05/09/2017. 22 | // 23 | 24 | import Foundation 25 | import HandyJSON 26 | 27 | struct BasicTypesInStruct: HandyJSON { 28 | var bool: Bool = true 29 | var boolOptional: Bool? 30 | var boolImplicitlyUnwrapped: Bool! 31 | var int: Int = 0 32 | var intOptional: Int? 33 | var intImplicitlyUnwrapped: Int! 34 | var double: Double = 0 35 | var doubleOptional: Double? 36 | var doubleImplicitlyUnwrapped: Double! 37 | var float: Float = 0 38 | var floatOptional: Float? 39 | var floatImplicitlyUnwrapped: Float! 40 | var string: String = "" 41 | var stringOptional: String? 42 | var stringImplicitlyUnwrapped: String! 43 | var anyObject: Any = true 44 | var anyObjectOptional: Any? 45 | var anyObjectImplicitlyUnwrapped: Any! 46 | 47 | var arrayBool: Array = [] 48 | var arrayBoolOptional: Array? 49 | var arrayBoolImplicitlyUnwrapped: Array! 50 | var arrayInt: Array = [] 51 | var arrayIntOptional: Array? 52 | var arrayIntImplicitlyUnwrapped: Array! 53 | var arrayDouble: Array = [] 54 | var arrayDoubleOptional: Array? 55 | var arrayDoubleImplicitlyUnwrapped: Array! 56 | var arrayFloat: Array = [] 57 | var arrayFloatOptional: Array? 58 | var arrayFloatImplicitlyUnwrapped: Array! 59 | var arrayString: Array = [] 60 | var arrayStringOptional: Array? 61 | var arrayStringImplicitlyUnwrapped: Array! 62 | var arrayAnyObject: Array = [] 63 | var arrayAnyObjectOptional: Array? 64 | var arrayAnyObjectImplicitlyUnwrapped: Array! 65 | 66 | var dictBool: Dictionary = [:] 67 | var dictBoolOptional: Dictionary? 68 | var dictBoolImplicitlyUnwrapped: Dictionary! 69 | var dictInt: Dictionary = [:] 70 | var dictIntOptional: Dictionary? 71 | var dictIntImplicitlyUnwrapped: Dictionary! 72 | var dictDouble: Dictionary = [:] 73 | var dictDoubleOptional: Dictionary? 74 | var dictDoubleImplicitlyUnwrapped: Dictionary! 75 | var dictFloat: Dictionary = [:] 76 | var dictFloatOptional: Dictionary? 77 | var dictFloatImplicitlyUnwrapped: Dictionary! 78 | var dictString: Dictionary = [:] 79 | var dictStringOptional: Dictionary? 80 | var dictStringImplicitlyUnwrapped: Dictionary! 81 | var dictAnyObject: Dictionary = [:] 82 | var dictAnyObjectOptional: Dictionary? 83 | var dictAnyObjectImplicitlyUnwrapped: Dictionary! 84 | 85 | var nsString: NSString = "" 86 | var nsStringOptional: NSString? 87 | var nsStringImplicitlyUnwrapped: NSString! 88 | 89 | var arrayNSString: Array = [] 90 | var arrayNSStringOptional: Array? 91 | var arrayNSStringImplicitlyUnwrapped: Array! 92 | 93 | var nsNumber: NSNumber = 0 94 | var nsNumberOptional: NSNumber? 95 | var nsNumberImplicitlyUnwrapped: NSNumber! 96 | 97 | var arrayNSNumber: Array = [] 98 | var arrayNSNumberOptional: Array? 99 | var arrayNSNumberImplicitlyUnwrapped: Array! 100 | 101 | var nsArray: NSArray = [] 102 | var nsArrayOptional: NSArray? 103 | var nsArrayImplicitlyUnwrapped: NSArray! 104 | 105 | var nsDictionary: NSDictionary = [:] 106 | var nsDictionaryOptional: NSDictionary? 107 | var nsDictionaryImplicitlyUnwrapped: NSDictionary! 108 | 109 | enum EnumInt: Int, HandyJSONEnum { 110 | case Default 111 | case Another 112 | } 113 | var enumInt: EnumInt = .Default 114 | var enumIntOptional: EnumInt? 115 | var enumIntImplicitlyUnwrapped: EnumInt! 116 | 117 | enum EnumDouble: Double, HandyJSONEnum { 118 | case Default 119 | case Another 120 | } 121 | var enumDouble: EnumDouble = .Default 122 | var enumDoubleOptional: EnumDouble? 123 | var enumDoubleImplicitlyUnwrapped: EnumDouble! 124 | 125 | enum EnumFloat: Float, HandyJSONEnum { 126 | case Default 127 | case Another 128 | } 129 | var enumFloat: EnumFloat = .Default 130 | var enumFloatOptional: EnumFloat? 131 | var enumFloatImplicitlyUnwrapped: EnumFloat! 132 | 133 | enum EnumString: String, HandyJSONEnum { 134 | case Default = "Default" 135 | case Another = "Another" 136 | } 137 | var enumString: EnumString = .Default 138 | var enumStringOptional: EnumString? 139 | var enumStringImplicitlyUnwrapped: EnumString! 140 | 141 | var arrayEnumInt: [EnumInt] = [] 142 | var arrayEnumIntOptional: [EnumInt]? 143 | var arrayEnumIntImplicitlyUnwrapped: [EnumInt]! 144 | 145 | var dictEnumInt: [String: EnumInt] = [:] 146 | var dictEnumIntOptional: [String: EnumInt]? 147 | var dictEnumIntImplicitlyUnwrapped: [String: EnumInt]! 148 | } 149 | -------------------------------------------------------------------------------- /Source/ContextDescriptorType.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 | // Created by zhouzhuo on 07/01/2017. 19 | // 20 | 21 | protocol ContextDescriptorType : MetadataType { 22 | var contextDescriptorOffsetLocation: Int { get } 23 | } 24 | 25 | extension ContextDescriptorType { 26 | 27 | var contextDescriptor: ContextDescriptorProtocol? { 28 | let pointer = UnsafePointer(self.pointer) 29 | let base = pointer.advanced(by: contextDescriptorOffsetLocation) 30 | if base.pointee == 0 { 31 | // swift class created dynamically in objc-runtime didn't have valid contextDescriptor 32 | return nil 33 | } 34 | if self.kind == .class { 35 | return ContextDescriptor<_ClassContextDescriptor>(pointer: relativePointer(base: base, offset: base.pointee - Int(bitPattern: base))) 36 | } else { 37 | return ContextDescriptor<_StructContextDescriptor>(pointer: relativePointer(base: base, offset: base.pointee - Int(bitPattern: base))) 38 | } 39 | } 40 | 41 | var contextDescriptorPointer: UnsafeRawPointer? { 42 | let pointer = UnsafePointer(self.pointer) 43 | let base = pointer.advanced(by: contextDescriptorOffsetLocation) 44 | if base.pointee == 0 { 45 | return nil 46 | } 47 | return UnsafeRawPointer(bitPattern: base.pointee) 48 | } 49 | 50 | // var genericArgumentVector: UnsafeRawPointer? { 51 | // let pointer = UnsafePointer(self.pointer) 52 | // let base = pointer.advanced(by: 19) 53 | // if base.pointee == 0 { 54 | // return nil 55 | // } 56 | // return UnsafeRawPointer(base) 57 | // } 58 | 59 | var mangledName: String { 60 | let pointer = UnsafePointer(self.pointer) 61 | let base = pointer.advanced(by: contextDescriptorOffsetLocation) 62 | let mangledNameAddress = base.pointee + 2 * 4 // 2 properties in front 63 | if let offset = contextDescriptor?.mangledName, 64 | let cString = UnsafePointer(bitPattern: mangledNameAddress + offset) { 65 | return String(cString: cString) 66 | } 67 | return "" 68 | } 69 | 70 | var numberOfFields: Int { 71 | return contextDescriptor?.numberOfFields ?? 0 72 | } 73 | 74 | var fieldOffsets: [Int]? { 75 | guard let contextDescriptor = self.contextDescriptor else { 76 | return nil 77 | } 78 | let vectorOffset = contextDescriptor.fieldOffsetVector 79 | guard vectorOffset != 0 else { 80 | return nil 81 | } 82 | if self.kind == .class { 83 | return (0..(pointer)[vectorOffset + $0] 85 | } 86 | } else { 87 | return (0..(pointer)[vectorOffset * (is64BitPlatform ? 2 : 1) + $0]) 89 | } 90 | } 91 | } 92 | 93 | var reflectionFieldDescriptor: FieldDescriptor? { 94 | guard let contextDescriptor = self.contextDescriptor else { 95 | return nil 96 | } 97 | let pointer = UnsafePointer(self.pointer) 98 | let base = pointer.advanced(by: contextDescriptorOffsetLocation) 99 | let offset = contextDescriptor.reflectionFieldDescriptor 100 | let address = base.pointee + 4 * 4 // (4 properties in front) * (sizeof Int32) 101 | guard let fieldDescriptorPtr = UnsafePointer<_FieldDescriptor>(bitPattern: address + offset) else { 102 | return nil 103 | } 104 | return FieldDescriptor(pointer: fieldDescriptorPtr) 105 | } 106 | } 107 | 108 | protocol ContextDescriptorProtocol { 109 | var mangledName: Int { get } 110 | var numberOfFields: Int { get } 111 | var fieldOffsetVector: Int { get } 112 | var reflectionFieldDescriptor: Int { get } 113 | } 114 | 115 | struct ContextDescriptor: ContextDescriptorProtocol, PointerType { 116 | 117 | var pointer: UnsafePointer 118 | 119 | var mangledName: Int { 120 | return Int(pointer.pointee.mangledNameOffset) 121 | } 122 | 123 | var numberOfFields: Int { 124 | return Int(pointer.pointee.numberOfFields) 125 | } 126 | 127 | var fieldOffsetVector: Int { 128 | return Int(pointer.pointee.fieldOffsetVector) 129 | } 130 | 131 | var fieldTypesAccessor: Int { 132 | return Int(pointer.pointee.fieldTypesAccessor) 133 | } 134 | 135 | var reflectionFieldDescriptor: Int { 136 | return Int(pointer.pointee.reflectionFieldDescriptor) 137 | } 138 | } 139 | 140 | protocol _ContextDescriptorProtocol { 141 | var mangledNameOffset: Int32 { get } 142 | var numberOfFields: Int32 { get } 143 | var fieldOffsetVector: Int32 { get } 144 | var fieldTypesAccessor: Int32 { get } 145 | var reflectionFieldDescriptor: Int32 { get } 146 | } 147 | 148 | struct _StructContextDescriptor: _ContextDescriptorProtocol { 149 | var flags: Int32 150 | var parent: Int32 151 | var mangledNameOffset: Int32 152 | var fieldTypesAccessor: Int32 153 | var reflectionFieldDescriptor: Int32 154 | var numberOfFields: Int32 155 | var fieldOffsetVector: Int32 156 | } 157 | 158 | struct _ClassContextDescriptor: _ContextDescriptorProtocol { 159 | var flags: Int32 160 | var parent: Int32 161 | var mangledNameOffset: Int32 162 | var fieldTypesAccessor: Int32 163 | var reflectionFieldDescriptor: Int32 164 | var superClsRef: Int32 165 | var metadataNegativeSizeInWords: Int32 166 | var metadataPositiveSizeInWords: Int32 167 | var numImmediateMembers: Int32 168 | var numberOfFields: Int32 169 | var fieldOffsetVector: Int32 170 | } 171 | -------------------------------------------------------------------------------- /Tests/HandyJSONTests/CustomTransfromTypes.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 | // CustomTransfromTypes.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 05/09/2017. 22 | // 23 | 24 | import XCTest 25 | import HandyJSON 26 | 27 | enum PureEnum { 28 | case type1, type2 29 | } 30 | 31 | extension PureEnum: HandyJSONCustomTransformable { 32 | 33 | static func _transform(from object: Any) -> PureEnum? { 34 | if let strValue = object as? String { 35 | return strValue == "type1" ? PureEnum.type1 : PureEnum.type2 36 | } 37 | return nil 38 | } 39 | 40 | func _plainValue() -> Any? { 41 | return "\(self)" 42 | } 43 | } 44 | 45 | struct EnumTestType: HandyJSON { 46 | var aStr: String? 47 | var aEnum: PureEnum? 48 | } 49 | 50 | struct CustomMappingStruct: HandyJSON { 51 | var name: String? 52 | var id: String? 53 | var height: Int? 54 | 55 | mutating func mapping(mapper: HelpingMapper) { 56 | // specify json field name 57 | mapper <<< 58 | self.name <-- "json_name" 59 | 60 | // specify converting method 61 | mapper <<< 62 | self.id <-- TransformOf(fromJSON: { (rawValue) -> String? in 63 | if let str = rawValue { 64 | return "json_" + str 65 | } 66 | return nil 67 | }, toJSON: { (id) -> String? in 68 | return id 69 | }) 70 | 71 | mapper <<< 72 | self.height <-- ("json_height", TransformOf(fromJSON: { (rawValue) -> Int? in 73 | if let _str = rawValue { 74 | return Int(_str) ?? 0 75 | } 76 | return nil 77 | }, toJSON: { (height) -> String? in 78 | if let _height = height { 79 | return "\(_height)" 80 | } 81 | return nil 82 | })) 83 | } 84 | } 85 | 86 | class CustomMappingClass: HandyJSON { 87 | var name: String? 88 | var id: String? 89 | var height: Int? 90 | 91 | required init() {} 92 | 93 | func mapping(mapper: HelpingMapper) { 94 | // specify json field name 95 | mapper <<< 96 | self.name <-- "json_name" 97 | 98 | // specify converting method 99 | mapper <<< 100 | self.id <-- TransformOf(fromJSON: { (rawStr) -> String? in 101 | if let _str = rawStr { 102 | return "json_" + _str 103 | } 104 | return nil 105 | }, toJSON: { (srcStr) -> String? in 106 | return srcStr 107 | }) 108 | 109 | // specify both 110 | mapper <<< 111 | self.height <-- ("json_height", TransformOf(fromJSON: { (rawInt) -> Int? in 112 | if let _int = rawInt { 113 | return _int / 100 114 | } 115 | return nil 116 | }, toJSON: { (srcInt) -> Int? in 117 | if let _int = srcInt { 118 | return _int * 100 119 | } 120 | return nil 121 | })) 122 | } 123 | } 124 | 125 | class KeyArrayMappingClass: HandyJSON { 126 | var var1: String? 127 | var var2: String? 128 | var var3: String? 129 | 130 | required init() {} 131 | 132 | func mapping(mapper: HelpingMapper) { 133 | mapper <<< 134 | self.var1 <-- ["var1_v1", "var1_v2", "var1_v3"] 135 | 136 | mapper <<< 137 | self.var2 <-- (["var2_v1", "var2_v2", "var2_v3"], TransformOf(fromJSON: { (rawStr) -> String? in 138 | return rawStr 139 | }, toJSON: { (srcStr) -> String? in 140 | return srcStr 141 | })) 142 | 143 | mapper <<< 144 | self.var3 <-- ["var3_v1"] 145 | } 146 | } 147 | 148 | struct NotHandyJSON { 149 | var empty: String? 150 | } 151 | 152 | class ExcludedMappingTestClass: HandyJSON { 153 | var notHandyJSONProperty: NotHandyJSON? 154 | var name: String? 155 | var id: String? 156 | var height: Int? 157 | 158 | required init() {} 159 | 160 | func mapping(mapper: HelpingMapper) { 161 | mapper >>> self.notHandyJSONProperty 162 | mapper >>> self.name 163 | } 164 | } 165 | 166 | struct ExcludedMappingTestStruct: HandyJSON { 167 | var name: String? 168 | var id: String? 169 | var height: Int? 170 | var notHandyJSONProperty: NotHandyJSON? 171 | 172 | mutating func mapping(mapper: HelpingMapper) { 173 | mapper >>> self.notHandyJSONProperty 174 | mapper >>> name 175 | } 176 | } 177 | 178 | class FlatLayerModel: HandyJSON { 179 | var enumMember: StringEnum = StringEnum.Default 180 | var enumMemberOptional: StringEnum? 181 | var enumMemberImplicitlyUnwrapped: StringEnum! 182 | var int: Int = 0 183 | var stringOptional: String? 184 | var dictBoolImplicitlyUnwrapped: [String: Bool]! 185 | 186 | func mapping(mapper: HelpingMapper) { 187 | mapper <<< 188 | enumMember <-- "lowerLayerModel.enumMember" 189 | 190 | mapper <<< 191 | enumMemberOptional <-- "lowerLayerModelOptional.enumMemberOptional" 192 | 193 | mapper <<< 194 | enumMemberImplicitlyUnwrapped <-- "lowerLayerModelImplicitlyUnwrapped.enumMemberImplicitlyUnwrapped" 195 | 196 | mapper <<< 197 | int <-- "classMember.int" 198 | 199 | mapper <<< 200 | stringOptional <-- "lowerLayerModelOptional.classMemberOptional.stringOptional" 201 | 202 | mapper <<< 203 | dictBoolImplicitlyUnwrapped <-- "lowerLayerModelImplicitlyUnwrapped.structMemberImplicitlyUnwrapped.dictBoolImplicitlyUnwrapped" 204 | } 205 | 206 | required init() {} 207 | } 208 | 209 | 210 | class DeepPathPropModel: HandyJSON { 211 | var enumMemberOptional: StringEnum? 212 | 213 | func mapping(mapper: HelpingMapper) { 214 | mapper <<< 215 | enumMemberOptional <-- ["serializeKey", "first layer.second\\.layer.thirdlayer.enumMemberOptional"] 216 | } 217 | 218 | required init() {} 219 | } 220 | -------------------------------------------------------------------------------- /Tests/HandyJSONTests/GenericTypesTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GenericTypesTest.swift 3 | // HandyJSON 4 | // 5 | // Created by chantu on 2019/3/27. 6 | // Copyright © 2019 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import HandyJSON 11 | import XCTest 12 | 13 | class GenericTypesTest: XCTestCase { 14 | 15 | func testNoSuperClass() { 16 | 17 | let basicCls = BasicTypesInClass() 18 | basicCls.intOptional = 123 19 | basicCls.arrayString = ["456", "789"] 20 | 21 | let cls = BaseGenericClass() 22 | cls.base = basicCls 23 | 24 | let json = cls.toJSONString() 25 | 26 | let deserializedCls = BaseGenericClass.deserialize(from: json) 27 | 28 | XCTAssertEqual(deserializedCls!.base!.intOptional, 123) 29 | XCTAssertEqual(deserializedCls!.base!.arrayString, ["456", "789"]) 30 | 31 | let inheritBasicCls = InheritanceBasicType() 32 | inheritBasicCls.intOptional = 123 33 | inheritBasicCls.arrayString = ["456", "789"] 34 | 35 | let cls1 = GenericWithNormalClass() 36 | cls1.t = inheritBasicCls 37 | 38 | let json1 = cls1.toJSONString() 39 | 40 | let deserializedCls1 = GenericWithNormalClass.deserialize(from: json1) 41 | 42 | XCTAssertEqual(deserializedCls1!.t!.intOptional, 123) 43 | XCTAssertEqual(deserializedCls1!.t!.arrayString, ["456", "789"]) 44 | } 45 | 46 | func testSuperClass() { 47 | 48 | let basicCls = BasicTypesInClass() 49 | basicCls.intOptional = 123 50 | basicCls.arrayString = ["456", "789"] 51 | let cls = SuperGenericClass() 52 | cls.t = basicCls 53 | 54 | let json = cls.toJSONString() 55 | 56 | let deserializedCls = SuperGenericClass.deserialize(from: json) 57 | 58 | XCTAssertEqual(deserializedCls!.t!.intOptional, 123) 59 | XCTAssertEqual(deserializedCls!.t!.arrayString, ["456", "789"]) 60 | 61 | let cls1 = SubGenericClassInheritGeneric() 62 | cls1.t = basicCls 63 | 64 | let json1 = cls1.toJSONString() 65 | 66 | let deserializedCls1 = SubGenericClassInheritGeneric.deserialize(from: json1) 67 | 68 | XCTAssertEqual(deserializedCls1!.t!.intOptional, 123) 69 | XCTAssertEqual(deserializedCls1!.t!.arrayString, ["456", "789"]) 70 | } 71 | 72 | func testSuperSuperClass() { 73 | 74 | let basicCls = BasicTypesInClass() 75 | basicCls.intOptional = 123 76 | basicCls.arrayString = ["456", "789"] 77 | let cls = SubGenericClassInheritGeneric() 78 | cls.t = basicCls 79 | 80 | let json = cls.toJSONString() 81 | 82 | let deserializedCls = SubGenericClassInheritGeneric.deserialize(from: json) 83 | 84 | XCTAssertEqual(deserializedCls!.t!.intOptional, 123) 85 | XCTAssertEqual(deserializedCls!.t!.arrayString, ["456", "789"]) 86 | 87 | let cls1 = SubGenericClass() 88 | cls1.sub = basicCls 89 | 90 | let json1 = cls1.toJSONString() 91 | 92 | let deserializedCls1 = SubGenericClass.deserialize(from: json1) 93 | 94 | XCTAssertEqual(deserializedCls1!.sub!.intOptional, 123) 95 | XCTAssertEqual(deserializedCls1!.sub!.arrayString, ["456", "789"]) 96 | 97 | let inheritBasicCls = InheritanceBasicType() 98 | inheritBasicCls.intOptional = 123 99 | inheritBasicCls.arrayString = ["456", "789"] 100 | 101 | let cls2 = SubGenericClassInheritGenericWithNormalClass() 102 | cls2.t = inheritBasicCls 103 | 104 | let json2 = cls2.toJSONString() 105 | 106 | let deserializedCls2 = SubGenericClassInheritGenericWithNormalClass.deserialize(from: json2) 107 | 108 | XCTAssertEqual(deserializedCls2!.t!.intOptional, 123) 109 | XCTAssertEqual(deserializedCls2!.t!.arrayString, ["456", "789"]) 110 | } 111 | 112 | func testStructClass() { 113 | 114 | var basicStruct = BasicTypesInStruct() 115 | basicStruct.intOptional = 123 116 | basicStruct.arrayString = ["456", "789"] 117 | var genericStruct = GenericStruct() 118 | genericStruct.t = basicStruct 119 | 120 | let json = genericStruct.toJSONString() 121 | 122 | let deserializedStruct = GenericStruct.deserialize(from: json) 123 | 124 | XCTAssertEqual(deserializedStruct!.t!.intOptional, 123) 125 | XCTAssertEqual(deserializedStruct!.t!.arrayString, ["456", "789"]) 126 | 127 | let basicCls = BasicTypesInClass() 128 | basicCls.intOptional = 123 129 | basicCls.arrayString = ["456", "789"] 130 | var genericStruct1 = GenericStruct() 131 | genericStruct1.t = basicCls 132 | 133 | let json1 = genericStruct1.toJSONString() 134 | 135 | let deserializedStruct1 = GenericStruct.deserialize(from: json1) 136 | 137 | XCTAssertEqual(deserializedStruct1!.t!.intOptional, 123) 138 | XCTAssertEqual(deserializedStruct1!.t!.arrayString, ["456", "789"]) 139 | } 140 | 141 | func testComplicatedGeneric() { 142 | 143 | let basicCls = BasicTypesInClass() 144 | basicCls.intOptional = 123 145 | basicCls.arrayString = ["456", "789"] 146 | 147 | let inheritBasicCls = InheritanceBasicType() 148 | inheritBasicCls.intOptional = 123 149 | inheritBasicCls.arrayString = ["456", "789"] 150 | 151 | var basicStruct = BasicTypesInStruct() 152 | basicStruct.intOptional = 123 153 | basicStruct.arrayString = ["456", "789"] 154 | 155 | let complicated = ComplicatedGenericClass() 156 | complicated.basicTypesInClass = inheritBasicCls 157 | complicated.inheritanceBasicType = inheritBasicCls 158 | complicated.handyJSONProtocol1 = basicStruct 159 | complicated.handyJSONProtocol2 = basicCls 160 | complicated.basicTypesInStruct = basicStruct 161 | 162 | let json = complicated.toJSONString() 163 | 164 | let deserializedComplicated = ComplicatedGenericClass.deserialize(from: json) 165 | 166 | XCTAssertEqual(deserializedComplicated!.basicTypesInClass!.intOptional, 123) 167 | XCTAssertEqual(deserializedComplicated!.basicTypesInClass!.arrayString, ["456", "789"]) 168 | XCTAssertEqual(deserializedComplicated!.inheritanceBasicType!.intOptional, 123) 169 | XCTAssertEqual(deserializedComplicated!.inheritanceBasicType!.arrayString, ["456", "789"]) 170 | XCTAssertEqual(deserializedComplicated!.handyJSONProtocol1!.intOptional, 123) 171 | XCTAssertEqual(deserializedComplicated!.handyJSONProtocol1!.arrayString, ["456", "789"]) 172 | XCTAssertEqual(deserializedComplicated!.handyJSONProtocol2!.intOptional, 123) 173 | XCTAssertEqual(deserializedComplicated!.handyJSONProtocol2!.arrayString, ["456", "789"]) 174 | XCTAssertEqual(deserializedComplicated!.basicTypesInStruct!.intOptional, 123) 175 | XCTAssertEqual(deserializedComplicated!.basicTypesInStruct!.arrayString, ["456", "789"]) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased](https://github.com/alibaba/HandyJSON/tree/HEAD) 4 | 5 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/1.3.0...HEAD) 6 | 7 | **Closed issues:** 8 | 9 | - sub class object missed during deserialization [\#53](https://github.com/alibaba/HandyJSON/issues/53) 10 | - pod 只找到0.1.0版本 [\#49](https://github.com/alibaba/HandyJSON/issues/49) 11 | 12 | ## [1.3.0](https://github.com/alibaba/HandyJSON/tree/1.3.0) (2016-11-22) 13 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/1.2.1...1.3.0) 14 | 15 | **Closed issues:** 16 | 17 | - deserializeModelArrayFrom 为什么没有 designatedPath参数? [\#48](https://github.com/alibaba/HandyJSON/issues/48) 18 | - 对于JSON中某个String字段是一个JSON字符串有没有简便的转化方式 [\#45](https://github.com/alibaba/HandyJSON/issues/45) 19 | - 能否实现Int到String的自动转换 [\#44](https://github.com/alibaba/HandyJSON/issues/44) 20 | - 对枚举类型的实现效果不佳 [\#43](https://github.com/alibaba/HandyJSON/issues/43) 21 | - Undefined symbols for architecture i386: [\#42](https://github.com/alibaba/HandyJSON/issues/42) 22 | - deserializeModelArrayFrom\(\)返回 \[T\]? 使用起来是不是会更方便一些? [\#40](https://github.com/alibaba/HandyJSON/issues/40) 23 | - 当model中某个String变量,存的是json字符格式解析不出来 [\#37](https://github.com/alibaba/HandyJSON/issues/37) 24 | - crash [\#36](https://github.com/alibaba/HandyJSON/issues/36) 25 | - 如何应对服务端和前端命名不一致的问题? [\#34](https://github.com/alibaba/HandyJSON/issues/34) 26 | - Carthage warning [\#32](https://github.com/alibaba/HandyJSON/issues/32) 27 | 28 | **Merged pull requests:** 29 | 30 | - add testcases/deserialize array support designating path [\#52](https://github.com/alibaba/HandyJSON/pull/52) ([xuyecan](https://github.com/xuyecan)) 31 | - fix warnning [\#51](https://github.com/alibaba/HandyJSON/pull/51) ([cijianzy](https://github.com/cijianzy)) 32 | - support enum perfectly [\#50](https://github.com/alibaba/HandyJSON/pull/50) ([xuyecan](https://github.com/xuyecan)) 33 | - fix designated path issue & add cn readme [\#47](https://github.com/alibaba/HandyJSON/pull/47) ([xuyecan](https://github.com/xuyecan)) 34 | - do some optimization & add testcases [\#46](https://github.com/alibaba/HandyJSON/pull/46) ([xuyecan](https://github.com/xuyecan)) 35 | - Use Swift's Syntactic Sugar [\#41](https://github.com/alibaba/HandyJSON/pull/41) ([wongzigii](https://github.com/wongzigii)) 36 | - update README.md, use `require` instead of `need`. [\#39](https://github.com/alibaba/HandyJSON/pull/39) ([swwlqw](https://github.com/swwlqw)) 37 | - Syntax highlightion in README [\#38](https://github.com/alibaba/HandyJSON/pull/38) ([wongzigii](https://github.com/wongzigii)) 38 | - Add iOS test target [\#35](https://github.com/alibaba/HandyJSON/pull/35) ([cijianzy](https://github.com/cijianzy)) 39 | 40 | ## [1.2.1](https://github.com/alibaba/HandyJSON/tree/1.2.1) (2016-11-04) 41 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/1.2.0...1.2.1) 42 | 43 | **Closed issues:** 44 | 45 | - Xcode8.1编译报错 [\#28](https://github.com/alibaba/HandyJSON/issues/28) 46 | 47 | ## [1.2.0](https://github.com/alibaba/HandyJSON/tree/1.2.0) (2016-11-01) 48 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/1.1.0...1.2.0) 49 | 50 | **Closed issues:** 51 | 52 | - 请问下一个版本大概什么时候能发布呢? [\#27](https://github.com/alibaba/HandyJSON/issues/27) 53 | - 模型中有一个,模型数组属性。 [\#25](https://github.com/alibaba/HandyJSON/issues/25) 54 | - How can I use this handsome library on Android platform [\#24](https://github.com/alibaba/HandyJSON/issues/24) 55 | - 请问有json数组转模型数组的方法么? [\#23](https://github.com/alibaba/HandyJSON/issues/23) 56 | - Support Linux [\#22](https://github.com/alibaba/HandyJSON/issues/22) 57 | - 为什么字典转模型时 传参数是 ObjectC 的 NSDictionary 类型 而不是 Swift 的 Collection 类型 [\#21](https://github.com/alibaba/HandyJSON/issues/21) 58 | - 小问题 [\#20](https://github.com/alibaba/HandyJSON/issues/20) 59 | - Manual Installation [\#13](https://github.com/alibaba/HandyJSON/issues/13) 60 | 61 | **Merged pull requests:** 62 | 63 | - ready for 1.2.0 [\#31](https://github.com/alibaba/HandyJSON/pull/31) ([xuyecan](https://github.com/xuyecan)) 64 | - support array formal json string [\#30](https://github.com/alibaba/HandyJSON/pull/30) ([xuyecan](https://github.com/xuyecan)) 65 | - fix compile error on xcode8 [\#29](https://github.com/alibaba/HandyJSON/pull/29) ([aixinyunchou](https://github.com/aixinyunchou)) 66 | - refactor serialization to support more features [\#26](https://github.com/alibaba/HandyJSON/pull/26) ([xuyecan](https://github.com/xuyecan)) 67 | - Fix docs link [\#19](https://github.com/alibaba/HandyJSON/pull/19) ([khasinski](https://github.com/khasinski)) 68 | - Add carthage badge [\#18](https://github.com/alibaba/HandyJSON/pull/18) ([cijianzy](https://github.com/cijianzy)) 69 | - add the manually installation section [\#17](https://github.com/alibaba/HandyJSON/pull/17) ([xuyecan](https://github.com/xuyecan)) 70 | 71 | ## [1.1.0](https://github.com/alibaba/HandyJSON/tree/1.1.0) (2016-10-05) 72 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/0.4.0...1.1.0) 73 | 74 | **Merged pull requests:** 75 | 76 | - Update file directory && Fix copy bundle [\#16](https://github.com/alibaba/HandyJSON/pull/16) ([cijianzy](https://github.com/cijianzy)) 77 | - Update file directory && Fix copy bundle [\#15](https://github.com/alibaba/HandyJSON/pull/15) ([cijianzy](https://github.com/cijianzy)) 78 | 79 | ## [0.4.0](https://github.com/alibaba/HandyJSON/tree/0.4.0) (2016-10-04) 80 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/1.0.0...0.4.0) 81 | 82 | **Merged pull requests:** 83 | 84 | - Support all platform [\#14](https://github.com/alibaba/HandyJSON/pull/14) ([cijianzy](https://github.com/cijianzy)) 85 | - code formatting [\#12](https://github.com/alibaba/HandyJSON/pull/12) ([xuyecan](https://github.com/xuyecan)) 86 | - add chinese doc link [\#11](https://github.com/alibaba/HandyJSON/pull/11) ([xuyecan](https://github.com/xuyecan)) 87 | - release 1.0.0 [\#10](https://github.com/alibaba/HandyJSON/pull/10) ([xuyecan](https://github.com/xuyecan)) 88 | 89 | ## [1.0.0](https://github.com/alibaba/HandyJSON/tree/1.0.0) (2016-10-01) 90 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/0.3.0...1.0.0) 91 | 92 | ## [0.3.0](https://github.com/alibaba/HandyJSON/tree/0.3.0) (2016-10-01) 93 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/0.2.0...0.3.0) 94 | 95 | **Closed issues:** 96 | 97 | - good job! [\#6](https://github.com/alibaba/HandyJSON/issues/6) 98 | 99 | **Merged pull requests:** 100 | 101 | - Travis-ci support swift3.0 [\#9](https://github.com/alibaba/HandyJSON/pull/9) ([cijianzy](https://github.com/cijianzy)) 102 | - migrate to swift 3.0 [\#8](https://github.com/alibaba/HandyJSON/pull/8) ([xuyecan](https://github.com/xuyecan)) 103 | - reorganize source files and optimize naming [\#7](https://github.com/alibaba/HandyJSON/pull/7) ([xuyecan](https://github.com/xuyecan)) 104 | 105 | ## [0.2.0](https://github.com/alibaba/HandyJSON/tree/0.2.0) (2016-09-27) 106 | [Full Changelog](https://github.com/alibaba/HandyJSON/compare/0.1.0...0.2.0) 107 | 108 | **Closed issues:** 109 | 110 | - struct can't have designated initializer. [\#3](https://github.com/alibaba/HandyJSON/issues/3) 111 | 112 | **Merged pull requests:** 113 | 114 | - Dev xyc [\#5](https://github.com/alibaba/HandyJSON/pull/5) ([xuyecan](https://github.com/xuyecan)) 115 | - Add codecov badge to master. [\#4](https://github.com/alibaba/HandyJSON/pull/4) ([cijianzy](https://github.com/cijianzy)) 116 | - Add badge [\#2](https://github.com/alibaba/HandyJSON/pull/2) ([cijianzy](https://github.com/cijianzy)) 117 | - Add support for serializtion [\#1](https://github.com/alibaba/HandyJSON/pull/1) ([cijianzy](https://github.com/cijianzy)) 118 | 119 | ## [0.1.0](https://github.com/alibaba/HandyJSON/tree/0.1.0) (2016-09-21) 120 | 121 | 122 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /Tests/HandyJSONTests/BasicTypesInClass.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 | // BasicTypesInClass.swift 19 | // HandyJSON 20 | // 21 | // Created by zhouzhuo on 05/01/2017. 22 | // 23 | 24 | import Foundation 25 | import HandyJSON 26 | 27 | class BasicTypesInClass: HandyJSON { 28 | var bool: Bool = true 29 | var boolOptional: Bool? 30 | var boolImplicitlyUnwrapped: Bool! 31 | var int: Int = 0 32 | var intOptional: Int? 33 | var intImplicitlyUnwrapped: Int! 34 | var double: Double = 0 35 | var doubleOptional: Double? 36 | var doubleImplicitlyUnwrapped: Double! 37 | var float: Float = 0 38 | var floatOptional: Float? 39 | var floatImplicitlyUnwrapped: Float! 40 | var string: String = "" 41 | var stringOptional: String? 42 | var stringImplicitlyUnwrapped: String! 43 | var anyObject: Any = true 44 | var anyObjectOptional: Any? 45 | var anyObjectImplicitlyUnwrapped: Any! 46 | 47 | var arrayBool: Array = [] 48 | var arrayBoolOptional: Array? 49 | var arrayBoolImplicitlyUnwrapped: Array! 50 | var arrayInt: Array = [] 51 | var arrayIntOptional: Array? 52 | var arrayIntImplicitlyUnwrapped: Array! 53 | var arrayDouble: Array = [] 54 | var arrayDoubleOptional: Array? 55 | var arrayDoubleImplicitlyUnwrapped: Array! 56 | var arrayFloat: Array = [] 57 | var arrayFloatOptional: Array? 58 | var arrayFloatImplicitlyUnwrapped: Array! 59 | var arrayString: Array = [] 60 | var arrayStringOptional: Array? 61 | var arrayStringImplicitlyUnwrapped: Array! 62 | var arrayAnyObject: Array = [] 63 | var arrayAnyObjectOptional: Array? 64 | var arrayAnyObjectImplicitlyUnwrapped: Array! 65 | 66 | var dictBool: Dictionary = [:] 67 | var dictBoolOptional: Dictionary? 68 | var dictBoolImplicitlyUnwrapped: Dictionary! 69 | var dictInt: Dictionary = [:] 70 | var dictIntOptional: Dictionary? 71 | var dictIntImplicitlyUnwrapped: Dictionary! 72 | var dictDouble: Dictionary = [:] 73 | var dictDoubleOptional: Dictionary? 74 | var dictDoubleImplicitlyUnwrapped: Dictionary! 75 | var dictFloat: Dictionary = [:] 76 | var dictFloatOptional: Dictionary? 77 | var dictFloatImplicitlyUnwrapped: Dictionary! 78 | var dictString: Dictionary = [:] 79 | var dictStringOptional: Dictionary? 80 | var dictStringImplicitlyUnwrapped: Dictionary! 81 | var dictAnyObject: Dictionary = [:] 82 | var dictAnyObjectOptional: Dictionary? 83 | var dictAnyObjectImplicitlyUnwrapped: Dictionary! 84 | 85 | var nsString: NSString = "" 86 | var nsStringOptional: NSString? 87 | var nsStringImplicitlyUnwrapped: NSString! 88 | 89 | var arrayNSString: Array = [] 90 | var arrayNSStringOptional: Array? 91 | var arrayNSStringImplicitlyUnwrapped: Array! 92 | 93 | var nsNumber: NSNumber = 0 94 | var nsNumberOptional: NSNumber? 95 | var nsNumberImplicitlyUnwrapped: NSNumber! 96 | 97 | var arrayNSNumber: Array = [] 98 | var arrayNSNumberOptional: Array? 99 | var arrayNSNumberImplicitlyUnwrapped: Array! 100 | 101 | var nsArray: NSArray = [] 102 | var nsArrayOptional: NSArray? 103 | var nsArrayImplicitlyUnwrapped: NSArray! 104 | 105 | var nsDictionary: NSDictionary = [:] 106 | var nsDictionaryOptional: NSDictionary? 107 | var nsDictionaryImplicitlyUnwrapped: NSDictionary! 108 | 109 | enum EnumInt: Int, HandyJSONEnum { 110 | case Default 111 | case Another 112 | } 113 | var enumInt: EnumInt = .Default 114 | var enumIntOptional: EnumInt? 115 | var enumIntImplicitlyUnwrapped: EnumInt! 116 | 117 | enum EnumDouble: Double, HandyJSONEnum { 118 | case Default 119 | case Another 120 | } 121 | var enumDouble: EnumDouble = .Default 122 | var enumDoubleOptional: EnumDouble? 123 | var enumDoubleImplicitlyUnwrapped: EnumDouble! 124 | 125 | enum EnumFloat: Float, HandyJSONEnum { 126 | case Default 127 | case Another 128 | } 129 | var enumFloat: EnumFloat = .Default 130 | var enumFloatOptional: EnumFloat? 131 | var enumFloatImplicitlyUnwrapped: EnumFloat! 132 | 133 | enum EnumString: String, HandyJSONEnum { 134 | case Default = "Default" 135 | case Another = "Another" 136 | } 137 | var enumString: EnumString = .Default 138 | var enumStringOptional: EnumString? 139 | var enumStringImplicitlyUnwrapped: EnumString! 140 | 141 | var arrayEnumInt: [EnumInt] = [] 142 | var arrayEnumIntOptional: [EnumInt]? 143 | var arrayEnumIntImplicitlyUnwrapped: [EnumInt]! 144 | 145 | var dictEnumInt: [String: EnumInt] = [:] 146 | var dictEnumIntOptional: [String: EnumInt]? 147 | var dictEnumIntImplicitlyUnwrapped: [String: EnumInt]! 148 | 149 | required init() {} 150 | } 151 | 152 | class TestCollectionOfPrimitives: HandyJSON { 153 | var dictStringString: [String: String] = [:] 154 | var dictStringInt: [String: Int] = [:] 155 | var dictStringBool: [String: Bool] = [:] 156 | var dictStringDouble: [String: Double] = [:] 157 | var dictStringFloat: [String: Float] = [:] 158 | 159 | var arrayString: [String] = [] 160 | var arrayInt: [Int] = [] 161 | var arrayBool: [Bool] = [] 162 | var arrayDouble: [Double] = [] 163 | var arrayFloat: [Float] = [] 164 | 165 | required init() {} 166 | } 167 | 168 | class InheritanceBasicType: BasicTypesInClass { 169 | 170 | var anotherInt: Int = 0 171 | var anotherIntOptional: Int? 172 | var anotherIntImplicitlyUnwrapped: Int! 173 | } 174 | 175 | class EmptyOCClass: NSObject, HandyJSON { 176 | override required init() { 177 | } 178 | } 179 | 180 | class InheritEmptyOCClass: EmptyOCClass { 181 | var int64: Int64 = 0 182 | var int: Int? 183 | var int32: Int32! 184 | var double: Double = 123.45 185 | } 186 | 187 | // iOS 13.4 beta crash 188 | class ObjcExtensionModel: HandyJSON { 189 | var id: String? 190 | 191 | required init() { 192 | } 193 | } 194 | class ObjcExtensionWithSuperModel: SuperObjcExtensionModel { 195 | var id: String? 196 | 197 | required init() { 198 | } 199 | } 200 | class ObjcExtensionWithOCSuperModel: SuperOCObjcExtensionModel { 201 | var id: String? 202 | 203 | required init() { 204 | } 205 | } 206 | class SuperOCObjcExtensionModel: NSObject, HandyJSON { 207 | var a: Int = 0x1234 208 | var b: Bool = false 209 | 210 | required override init() { 211 | } 212 | } 213 | class SuperObjcExtensionModel: HandyJSON { 214 | var a: Int = 0x1234 215 | var b: Bool = false 216 | 217 | required init() { 218 | } 219 | } 220 | // 条件1:@objc protocol 221 | @objc protocol ObjcExtensionProtocol { 222 | } 223 | // 条件2:extension 224 | extension ObjcExtensionModel: ObjcExtensionProtocol { 225 | } 226 | extension ObjcExtensionWithSuperModel: ObjcExtensionProtocol { 227 | } 228 | extension ObjcExtensionWithOCSuperModel: ObjcExtensionProtocol { 229 | } 230 | -------------------------------------------------------------------------------- /Tests/HandyJSONTests/CustomTransformTest.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 | // CustomTransformTest.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 | let jsonString = "{\"json_name\":\"Bob\",\"id\":\"12345\",\"json_height\":\"180\"}" 41 | let a = CustomMappingStruct.deserialize(from: jsonString)! 42 | XCTAssert(a.name == "Bob") 43 | XCTAssert(a.id == "json_12345") 44 | XCTAssert(a.height == 180) 45 | } 46 | 47 | func testMultipleNamesMapToOneProperty() { 48 | var jsonString = "{\"var1_v1\":\"var1_value\",\"var2_v1\":\"var2_value\",\"var3_v1\":\"var3_value\"}" 49 | var a = KeyArrayMappingClass.deserialize(from: jsonString)! 50 | XCTAssert(a.var1 == "var1_value") 51 | XCTAssert(a.var2 == "var2_value") 52 | XCTAssert(a.var3 == "var3_value") 53 | 54 | let toJson = a.toJSON()! 55 | XCTAssert(toJson["var1_v1"] as? String == "var1_value") 56 | XCTAssert(toJson["var1_v2"] as? String == nil) 57 | XCTAssert(toJson["var1_v3"] as? String == nil) 58 | XCTAssert(toJson["var2_v1"] as? String == "var2_value") 59 | XCTAssert(toJson["var2_v2"] as? String == nil) 60 | XCTAssert(toJson["var2_v3"] as? String == nil) 61 | XCTAssert(toJson["var3_v1"] as? String == "var3_value") 62 | 63 | jsonString = "{\"var1_v2\":\"var1_value\",\"var2_v2\":\"var2_value\",\"var3_v2\":\"var3_value\"}" 64 | a = KeyArrayMappingClass.deserialize(from: jsonString)! 65 | XCTAssert(a.var1 == "var1_value") 66 | XCTAssert(a.var2 == "var2_value") 67 | XCTAssert(a.var3 == nil) 68 | 69 | jsonString = "{\"var1\":\"var1_value\",\"var2\":\"var2_value\",\"var3\":\"var3_value\"}" 70 | a = KeyArrayMappingClass.deserialize(from: jsonString)! 71 | XCTAssert(a.var1 == nil) 72 | XCTAssert(a.var2 == nil) 73 | XCTAssert(a.var3 == nil) 74 | } 75 | 76 | func testClassMapping() { 77 | let jsonString = "{\"json_name\":\"Bob\",\"id\":\"12345\",\"json_height\":18000}" 78 | let a = CustomMappingClass.deserialize(from: jsonString)! 79 | XCTAssert(a.name == "Bob") 80 | XCTAssert(a.id == "json_12345") 81 | XCTAssert(a.height == 180) 82 | } 83 | 84 | func testExlucdePropertyForClass() { 85 | let jsonString = "{\"name\":\"Bob\",\"id\":\"12345\",\"height\":180}" 86 | let a = ExcludedMappingTestClass.deserialize(from: jsonString)! 87 | XCTAssert(a.name == nil) 88 | XCTAssert(a.id == "12345") 89 | XCTAssert(a.height == 180) 90 | } 91 | 92 | func testExcludedPropertyForStruct() { 93 | let jsonString = "{\"name\":\"Bob\",\"id\":\"12345\",\"height\":180, \"notHandyJSONProperty\":\"value\"}" 94 | let a = ExcludedMappingTestStruct.deserialize(from: jsonString)! 95 | XCTAssert(a.name == nil) 96 | XCTAssert(a.id == "12345") 97 | XCTAssert(a.height == 180) 98 | } 99 | 100 | func testCustomTransformableType() { 101 | let jsonString = "{\"aStr\":\"Bob\",\"aEnum\":\"type2\"}" 102 | let object = EnumTestType.deserialize(from: jsonString)! 103 | XCTAssertEqual(PureEnum.type2, object.aEnum!) 104 | } 105 | 106 | func testSpecifyPropertyPathMapping() { 107 | let basicClassModel = BasicTypesInClass() 108 | basicClassModel.int = 11 109 | basicClassModel.stringOptional = "stringOptional" 110 | basicClassModel.arrayIntImplicitlyUnwrapped = [1, 2, 3, 4, 5] 111 | 112 | var basicStructModel = BasicTypesInStruct() 113 | basicStructModel.enumInt = BasicTypesInStruct.EnumInt.Another 114 | basicStructModel.nsArrayOptional = ["one", "two", "three"] 115 | basicStructModel.dictBoolImplicitlyUnwrapped = ["true": true, "false": false] 116 | 117 | let lowerModel = LowerLayerModel() 118 | lowerModel.enumMember = StringEnum.Another 119 | lowerModel.enumMemberOptional = StringEnum.Another 120 | lowerModel.enumMemberImplicitlyUnwrapped = StringEnum.Another 121 | lowerModel.classMember = basicClassModel 122 | lowerModel.classMemberOptional = basicClassModel 123 | lowerModel.classMemberImplicitlyUnwrapped = basicClassModel 124 | lowerModel.structMember = basicStructModel 125 | lowerModel.structMemberOptional = basicStructModel 126 | lowerModel.structMemberImplicitlyUnwrapped = basicStructModel 127 | 128 | let topmostModel = TopMostLayerModel() 129 | topmostModel.classMember = basicClassModel 130 | topmostModel.classMemberOptional = basicClassModel 131 | topmostModel.classMemberImplicitlyUnwrapped = basicClassModel 132 | topmostModel.structMember = basicStructModel 133 | topmostModel.structMemberOptional = basicStructModel 134 | topmostModel.structMemberImplicitlyUnwrapped = basicStructModel 135 | topmostModel.lowerLayerModel = lowerModel 136 | topmostModel.lowerLayerModelOptional = lowerModel 137 | topmostModel.lowerLayerModelImplicitlyUnwrapped = lowerModel 138 | 139 | let dict = topmostModel.toJSON()! 140 | let mappedObject1 = FlatLayerModel.deserialize(from: dict)! 141 | 142 | XCTAssertEqual(mappedObject1.enumMember, StringEnum.Another) 143 | XCTAssertEqual(mappedObject1.enumMemberOptional, StringEnum.Another) 144 | XCTAssertEqual(mappedObject1.enumMemberImplicitlyUnwrapped, StringEnum.Another) 145 | XCTAssertEqual(mappedObject1.int, 11) 146 | XCTAssertEqual(mappedObject1.stringOptional, "stringOptional") 147 | XCTAssertEqual(mappedObject1.dictBoolImplicitlyUnwrapped["false"], false) 148 | 149 | let jsonString = topmostModel.toJSONString()! 150 | let mappedObject2 = FlatLayerModel.deserialize(from: jsonString)! 151 | 152 | XCTAssertEqual(mappedObject2.enumMember, StringEnum.Another) 153 | XCTAssertEqual(mappedObject2.enumMemberOptional, StringEnum.Another) 154 | XCTAssertEqual(mappedObject2.enumMemberImplicitlyUnwrapped, StringEnum.Another) 155 | XCTAssertEqual(mappedObject2.int, 11) 156 | XCTAssertEqual(mappedObject2.stringOptional, "stringOptional") 157 | XCTAssertEqual(mappedObject2.dictBoolImplicitlyUnwrapped["false"], false) 158 | } 159 | 160 | func testMappingDeepPathPropModelFromJSON() { 161 | let jsonString = "{\"first layer\":{\"second.layer\":{\"thirdlayer\":{\"enumMemberOptional\":\"Another\"}}}}" 162 | let mappedObject = DeepPathPropModel.deserialize(from: jsonString)! 163 | 164 | XCTAssertEqual(StringEnum.Another, mappedObject.enumMemberOptional) 165 | 166 | let serializeJSON = mappedObject.toJSON()! 167 | 168 | XCTAssertEqual(StringEnum.Another.rawValue, serializeJSON["serializeKey"] as! String) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Source/BuiltInBasicType.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 | protocol _BuiltInBasicType: _Transformable { 23 | 24 | static func _transform(from object: Any) -> Self? 25 | func _plainValue() -> Any? 26 | } 27 | 28 | // Suppport integer type 29 | 30 | protocol IntegerPropertyProtocol: FixedWidthInteger, _BuiltInBasicType { 31 | init?(_ text: String, radix: Int) 32 | init(_ number: NSNumber) 33 | } 34 | 35 | extension IntegerPropertyProtocol { 36 | 37 | static func _transform(from object: Any) -> Self? { 38 | switch object { 39 | case let str as String: 40 | return Self(str, radix: 10) 41 | case let num as NSNumber: 42 | return Self(num) 43 | default: 44 | return nil 45 | } 46 | } 47 | 48 | func _plainValue() -> Any? { 49 | return self 50 | } 51 | } 52 | 53 | extension Int: IntegerPropertyProtocol {} 54 | extension UInt: IntegerPropertyProtocol {} 55 | extension Int8: IntegerPropertyProtocol {} 56 | extension Int16: IntegerPropertyProtocol {} 57 | extension Int32: IntegerPropertyProtocol {} 58 | extension Int64: IntegerPropertyProtocol {} 59 | extension UInt8: IntegerPropertyProtocol {} 60 | extension UInt16: IntegerPropertyProtocol {} 61 | extension UInt32: IntegerPropertyProtocol {} 62 | extension UInt64: IntegerPropertyProtocol {} 63 | 64 | extension Bool: _BuiltInBasicType { 65 | 66 | static func _transform(from object: Any) -> Bool? { 67 | switch object { 68 | case let str as NSString: 69 | let lowerCase = str.lowercased 70 | if ["0", "false"].contains(lowerCase) { 71 | return false 72 | } 73 | if ["1", "true"].contains(lowerCase) { 74 | return true 75 | } 76 | return nil 77 | case let num as NSNumber: 78 | return num.boolValue 79 | default: 80 | return nil 81 | } 82 | } 83 | 84 | func _plainValue() -> Any? { 85 | return self 86 | } 87 | } 88 | 89 | // Support float type 90 | 91 | protocol FloatPropertyProtocol: _BuiltInBasicType, LosslessStringConvertible { 92 | init(_ number: NSNumber) 93 | } 94 | 95 | extension FloatPropertyProtocol { 96 | 97 | static func _transform(from object: Any) -> Self? { 98 | switch object { 99 | case let str as String: 100 | return Self(str) 101 | case let num as NSNumber: 102 | return Self(num) 103 | default: 104 | return nil 105 | } 106 | } 107 | 108 | func _plainValue() -> Any? { 109 | return self 110 | } 111 | } 112 | 113 | extension Float: FloatPropertyProtocol {} 114 | extension Double: FloatPropertyProtocol {} 115 | 116 | fileprivate let formatter: NumberFormatter = { 117 | let formatter = NumberFormatter() 118 | formatter.usesGroupingSeparator = false 119 | formatter.numberStyle = .decimal 120 | formatter.maximumFractionDigits = 16 121 | return formatter 122 | }() 123 | 124 | extension String: _BuiltInBasicType { 125 | 126 | static func _transform(from object: Any) -> String? { 127 | switch object { 128 | case let str as String: 129 | return str 130 | case let num as NSNumber: 131 | // Boolean Type Inside 132 | if NSStringFromClass(type(of: num)) == "__NSCFBoolean" { 133 | if num.boolValue { 134 | return "true" 135 | } else { 136 | return "false" 137 | } 138 | } 139 | return formatter.string(from: num) 140 | case _ as NSNull: 141 | return nil 142 | default: 143 | return "\(object)" 144 | } 145 | } 146 | 147 | func _plainValue() -> Any? { 148 | return self 149 | } 150 | } 151 | 152 | // MARK: Optional Support 153 | 154 | extension Optional: _BuiltInBasicType { 155 | 156 | static func _transform(from object: Any) -> Optional? { 157 | if let value = (Wrapped.self as? _Transformable.Type)?.transform(from: object) as? Wrapped { 158 | return Optional(value) 159 | } else if let value = object as? Wrapped { 160 | return Optional(value) 161 | } 162 | return nil 163 | } 164 | 165 | func _getWrappedValue() -> Any? { 166 | return self.map( { (wrapped) -> Any in 167 | return wrapped as Any 168 | }) 169 | } 170 | 171 | func _plainValue() -> Any? { 172 | if let value = _getWrappedValue() { 173 | if let transformable = value as? _Transformable { 174 | return transformable.plainValue() 175 | } else { 176 | return value 177 | } 178 | } 179 | return nil 180 | } 181 | } 182 | 183 | // MARK: Collection Support : Array & Set 184 | 185 | extension Collection { 186 | 187 | static func _collectionTransform(from object: Any) -> [Iterator.Element]? { 188 | guard let arr = object as? [Any] else { 189 | InternalLogger.logDebug("Expect object to be an array but it's not") 190 | return nil 191 | } 192 | typealias Element = Iterator.Element 193 | var result: [Element] = [Element]() 194 | arr.forEach { (each) in 195 | if let element = (Element.self as? _Transformable.Type)?.transform(from: each) as? Element { 196 | result.append(element) 197 | } else if let element = each as? Element { 198 | result.append(element) 199 | } 200 | } 201 | return result 202 | } 203 | 204 | func _collectionPlainValue() -> Any? { 205 | typealias Element = Iterator.Element 206 | var result: [Any] = [Any]() 207 | self.forEach { (each) in 208 | if let transformable = each as? _Transformable, let transValue = transformable.plainValue() { 209 | result.append(transValue) 210 | } else { 211 | InternalLogger.logError("value: \(each) isn't transformable type!") 212 | } 213 | } 214 | return result 215 | } 216 | } 217 | 218 | extension Array: _BuiltInBasicType { 219 | 220 | static func _transform(from object: Any) -> [Element]? { 221 | return self._collectionTransform(from: object) 222 | } 223 | 224 | func _plainValue() -> Any? { 225 | return self._collectionPlainValue() 226 | } 227 | } 228 | 229 | extension Set: _BuiltInBasicType { 230 | 231 | static func _transform(from object: Any) -> Set? { 232 | if let arr = self._collectionTransform(from: object) { 233 | return Set(arr) 234 | } 235 | return nil 236 | } 237 | 238 | func _plainValue() -> Any? { 239 | return self._collectionPlainValue() 240 | } 241 | } 242 | 243 | // MARK: Dictionary Support 244 | 245 | extension Dictionary: _BuiltInBasicType { 246 | 247 | static func _transform(from object: Any) -> [Key: Value]? { 248 | guard let dict = object as? [String: Any] else { 249 | InternalLogger.logDebug("Expect object to be an NSDictionary but it's not") 250 | return nil 251 | } 252 | var result = [Key: Value]() 253 | for (key, value) in dict { 254 | if let sKey = key as? Key { 255 | if let nValue = (Value.self as? _Transformable.Type)?.transform(from: value) as? Value { 256 | result[sKey] = nValue 257 | } else if let nValue = value as? Value { 258 | result[sKey] = nValue 259 | } 260 | } 261 | } 262 | return result 263 | } 264 | 265 | func _plainValue() -> Any? { 266 | var result = [String: Any]() 267 | for (key, value) in self { 268 | if let key = key as? String { 269 | if let transformable = value as? _Transformable { 270 | if let transValue = transformable.plainValue() { 271 | result[key] = transValue 272 | } 273 | } 274 | } 275 | } 276 | return result 277 | } 278 | } 279 | 280 | -------------------------------------------------------------------------------- /Source/Deserializer.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 extension HandyJSON { 23 | 24 | /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and converts it to a Model 25 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer 26 | static func deserialize(from dict: NSDictionary?, designatedPath: String? = nil) -> Self? { 27 | return deserialize(from: dict as? [String: Any], designatedPath: designatedPath) 28 | } 29 | 30 | /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and converts it to a Model 31 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer 32 | static func deserialize(from dict: [String: Any]?, designatedPath: String? = nil) -> Self? { 33 | return JSONDeserializer.deserializeFrom(dict: dict, designatedPath: designatedPath) 34 | } 35 | 36 | /// Finds the internal JSON field in `json` as the `designatedPath` specified, and converts it to a Model 37 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer 38 | static func deserialize(from json: String?, designatedPath: String? = nil) -> Self? { 39 | return JSONDeserializer.deserializeFrom(json: json, designatedPath: designatedPath) 40 | } 41 | } 42 | 43 | public extension Array where Element: HandyJSON { 44 | 45 | /// if the JSON field finded by `designatedPath` in `json` is representing a array, such as `[{...}, {...}, {...}]`, 46 | /// this method converts it to a Models array 47 | static func deserialize(from json: String?, designatedPath: String? = nil) -> [Element?]? { 48 | return JSONDeserializer.deserializeModelArrayFrom(json: json, designatedPath: designatedPath) 49 | } 50 | 51 | /// deserialize model array from NSArray 52 | static func deserialize(from array: NSArray?) -> [Element?]? { 53 | return JSONDeserializer.deserializeModelArrayFrom(array: array) 54 | } 55 | 56 | /// deserialize model array from array 57 | static func deserialize(from array: [Any]?) -> [Element?]? { 58 | return JSONDeserializer.deserializeModelArrayFrom(array: array) 59 | } 60 | } 61 | 62 | public class JSONDeserializer { 63 | 64 | /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and map it to a Model 65 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil 66 | public static func deserializeFrom(dict: NSDictionary?, designatedPath: String? = nil) -> T? { 67 | return deserializeFrom(dict: dict as? [String: Any], designatedPath: designatedPath) 68 | } 69 | 70 | /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and map it to a Model 71 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil 72 | public static func deserializeFrom(dict: [String: Any]?, designatedPath: String? = nil) -> T? { 73 | var targetDict = dict 74 | if let path = designatedPath { 75 | targetDict = getInnerObject(inside: targetDict, by: path) as? [String: Any] 76 | } 77 | if let _dict = targetDict { 78 | return T._transform(dict: _dict) as? T 79 | } 80 | return nil 81 | } 82 | 83 | /// Finds the internal JSON field in `json` as the `designatedPath` specified, and converts it to Model 84 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil 85 | public static func deserializeFrom(json: String?, designatedPath: String? = nil) -> T? { 86 | guard let _json = json else { 87 | return nil 88 | } 89 | do { 90 | let jsonObject = try JSONSerialization.jsonObject(with: _json.data(using: String.Encoding.utf8)!, options: .allowFragments) 91 | if let jsonDict = jsonObject as? NSDictionary { 92 | return self.deserializeFrom(dict: jsonDict, designatedPath: designatedPath) 93 | } 94 | } catch let error { 95 | InternalLogger.logError(error) 96 | } 97 | return nil 98 | } 99 | 100 | /// Finds the internal dictionary in `dict` as the `designatedPath` specified, and use it to reassign an exist model 101 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil 102 | public static func update(object: inout T, from dict: [String: Any]?, designatedPath: String? = nil) { 103 | var targetDict = dict 104 | if let path = designatedPath { 105 | targetDict = getInnerObject(inside: targetDict, by: path) as? [String: Any] 106 | } 107 | if let _dict = targetDict { 108 | T._transform(dict: _dict, to: &object) 109 | } 110 | } 111 | 112 | /// Finds the internal JSON field in `json` as the `designatedPath` specified, and use it to reassign an exist model 113 | /// `designatedPath` is a string like `result.data.orderInfo`, which each element split by `.` represents key of each layer, or nil 114 | public static func update(object: inout T, from json: String?, designatedPath: String? = nil) { 115 | guard let _json = json else { 116 | return 117 | } 118 | do { 119 | let jsonObject = try JSONSerialization.jsonObject(with: _json.data(using: String.Encoding.utf8)!, options: .allowFragments) 120 | if let jsonDict = jsonObject as? [String: Any] { 121 | update(object: &object, from: jsonDict, designatedPath: designatedPath) 122 | } 123 | } catch let error { 124 | InternalLogger.logError(error) 125 | } 126 | } 127 | 128 | /// if the JSON field found by `designatedPath` in `json` is representing a array, such as `[{...}, {...}, {...}]`, 129 | /// this method converts it to a Models array 130 | public static func deserializeModelArrayFrom(json: String?, designatedPath: String? = nil) -> [T?]? { 131 | guard let _json = json else { 132 | return nil 133 | } 134 | do { 135 | let jsonObject = try JSONSerialization.jsonObject(with: _json.data(using: String.Encoding.utf8)!, options: .allowFragments) 136 | if let jsonArray = getInnerObject(inside: jsonObject, by: designatedPath) as? [Any] { 137 | return jsonArray.map({ (item) -> T? in 138 | return self.deserializeFrom(dict: item as? [String: Any]) 139 | }) 140 | } 141 | } catch let error { 142 | InternalLogger.logError(error) 143 | } 144 | return nil 145 | } 146 | 147 | /// mapping raw array to Models array 148 | public static func deserializeModelArrayFrom(array: NSArray?) -> [T?]? { 149 | return deserializeModelArrayFrom(array: array as? [Any]) 150 | } 151 | 152 | /// mapping raw array to Models array 153 | public static func deserializeModelArrayFrom(array: [Any]?) -> [T?]? { 154 | guard let _arr = array else { 155 | return nil 156 | } 157 | return _arr.map({ (item) -> T? in 158 | return self.deserializeFrom(dict: item as? NSDictionary) 159 | }) 160 | } 161 | } 162 | 163 | fileprivate func getInnerObject(inside object: Any?, by designatedPath: String?) -> Any? { 164 | var result: Any? = object 165 | var abort = false 166 | if let paths = designatedPath?.components(separatedBy: "."), paths.count > 0 { 167 | var next = object as? [String: Any] 168 | paths.forEach({ (seg) in 169 | if seg.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "" || abort { 170 | return 171 | } 172 | if let _next = next?[seg] { 173 | result = _next 174 | next = _next as? [String: Any] 175 | } else { 176 | abort = true 177 | } 178 | }) 179 | } 180 | return abort ? nil : result 181 | } 182 | -------------------------------------------------------------------------------- /Source/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 typealias CustomMappingKeyValueTuple = (Int, MappingPropertyHandler) 23 | 24 | struct MappingPath { 25 | var segments: [String] 26 | 27 | static func buildFrom(rawPath: String) -> MappingPath { 28 | let regex = try! NSRegularExpression(pattern: "(? Any? { 52 | var currentDict: [String: Any]? = self 53 | var lastValue: Any? 54 | path.segments.forEach { (segment) in 55 | lastValue = currentDict?[segment] 56 | currentDict = currentDict?[segment] as? [String: Any] 57 | } 58 | return lastValue 59 | } 60 | } 61 | 62 | public class MappingPropertyHandler { 63 | var mappingPaths: [MappingPath]? 64 | var assignmentClosure: ((Any?) -> (Any?))? 65 | var takeValueClosure: ((Any?) -> (Any?))? 66 | 67 | public init(rawPaths: [String]?, assignmentClosure: ((Any?) -> (Any?))?, takeValueClosure: ((Any?) -> (Any?))?) { 68 | let mappingPaths = rawPaths?.map({ (rawPath) -> MappingPath in 69 | if HandyJSONConfiguration.deserializeOptions.contains(.caseInsensitive) { 70 | return MappingPath.buildFrom(rawPath: rawPath.lowercased()) 71 | } 72 | return MappingPath.buildFrom(rawPath: rawPath) 73 | }).filter({ (mappingPath) -> Bool in 74 | return mappingPath.segments.count > 0 75 | }) 76 | if let count = mappingPaths?.count, count > 0 { 77 | self.mappingPaths = mappingPaths 78 | } 79 | self.assignmentClosure = assignmentClosure 80 | self.takeValueClosure = takeValueClosure 81 | } 82 | } 83 | 84 | public class HelpingMapper { 85 | 86 | private var mappingHandlers = [Int: MappingPropertyHandler]() 87 | private var excludeProperties = [Int]() 88 | 89 | internal func getMappingHandler(key: Int) -> MappingPropertyHandler? { 90 | return self.mappingHandlers[key] 91 | } 92 | 93 | internal func propertyExcluded(key: Int) -> Bool { 94 | return self.excludeProperties.contains(key) 95 | } 96 | 97 | public func specify(property: inout T, name: String) { 98 | self.specify(property: &property, name: name, converter: nil) 99 | } 100 | 101 | public func specify(property: inout T, converter: @escaping (String) -> T) { 102 | self.specify(property: &property, name: nil, converter: converter) 103 | } 104 | 105 | public func specify(property: inout T, name: String?, converter: ((String) -> T)?) { 106 | let pointer = withUnsafePointer(to: &property, { return $0 }) 107 | let key = Int(bitPattern: pointer) 108 | let names = (name == nil ? nil : [name!]) 109 | 110 | if let _converter = converter { 111 | let assignmentClosure = { (jsonValue: Any?) -> Any? in 112 | if let _value = jsonValue{ 113 | if let object = _value as? NSObject { 114 | if let str = String.transform(from: object){ 115 | return _converter(str) 116 | } 117 | } 118 | } 119 | return nil 120 | } 121 | self.mappingHandlers[key] = MappingPropertyHandler(rawPaths: names, assignmentClosure: assignmentClosure, takeValueClosure: nil) 122 | } else { 123 | self.mappingHandlers[key] = MappingPropertyHandler(rawPaths: names, assignmentClosure: nil, takeValueClosure: nil) 124 | } 125 | } 126 | 127 | public func exclude(property: inout T) { 128 | self._exclude(property: &property) 129 | } 130 | 131 | fileprivate func addCustomMapping(key: Int, mappingInfo: MappingPropertyHandler) { 132 | self.mappingHandlers[key] = mappingInfo 133 | } 134 | 135 | fileprivate func _exclude(property: inout T) { 136 | let pointer = withUnsafePointer(to: &property, { return $0 }) 137 | self.excludeProperties.append(Int(bitPattern: pointer)) 138 | } 139 | } 140 | 141 | infix operator <-- : LogicalConjunctionPrecedence 142 | 143 | public func <-- (property: inout T, name: String) -> CustomMappingKeyValueTuple { 144 | return property <-- [name] 145 | } 146 | 147 | public func <-- (property: inout T, names: [String]) -> CustomMappingKeyValueTuple { 148 | let pointer = withUnsafePointer(to: &property, { return $0 }) 149 | let key = Int(bitPattern: pointer) 150 | return (key, MappingPropertyHandler(rawPaths: names, assignmentClosure: nil, takeValueClosure: nil)) 151 | } 152 | 153 | // MARK: non-optional properties 154 | public func <-- (property: inout Transform.Object, transformer: Transform) -> CustomMappingKeyValueTuple { 155 | return property <-- (nil, transformer) 156 | } 157 | 158 | public func <-- (property: inout Transform.Object, transformer: (String?, Transform?)) -> CustomMappingKeyValueTuple { 159 | let names = (transformer.0 == nil ? [] : [transformer.0!]) 160 | return property <-- (names, transformer.1) 161 | } 162 | 163 | public func <-- (property: inout Transform.Object, transformer: ([String], Transform?)) -> CustomMappingKeyValueTuple { 164 | let pointer = withUnsafePointer(to: &property, { return $0 }) 165 | let key = Int(bitPattern: pointer) 166 | let assignmentClosure = { (jsonValue: Any?) -> Transform.Object? in 167 | return transformer.1?.transformFromJSON(jsonValue) 168 | } 169 | let takeValueClosure = { (objectValue: Any?) -> Any? in 170 | if let _value = objectValue as? Transform.Object { 171 | return transformer.1?.transformToJSON(_value) as Any 172 | } 173 | return nil 174 | } 175 | return (key, MappingPropertyHandler(rawPaths: transformer.0, assignmentClosure: assignmentClosure, takeValueClosure: takeValueClosure)) 176 | } 177 | 178 | // MARK: optional properties 179 | public func <-- (property: inout Transform.Object?, transformer: Transform) -> CustomMappingKeyValueTuple { 180 | return property <-- (nil, transformer) 181 | } 182 | 183 | public func <-- (property: inout Transform.Object?, transformer: (String?, Transform?)) -> CustomMappingKeyValueTuple { 184 | let names = (transformer.0 == nil ? [] : [transformer.0!]) 185 | return property <-- (names, transformer.1) 186 | } 187 | 188 | public func <-- (property: inout Transform.Object?, transformer: ([String], Transform?)) -> CustomMappingKeyValueTuple { 189 | let pointer = withUnsafePointer(to: &property, { return $0 }) 190 | let key = Int(bitPattern: pointer) 191 | let assignmentClosure = { (jsonValue: Any?) -> Any? in 192 | return transformer.1?.transformFromJSON(jsonValue) 193 | } 194 | let takeValueClosure = { (objectValue: Any?) -> Any? in 195 | if let _value = objectValue as? Transform.Object { 196 | return transformer.1?.transformToJSON(_value) as Any 197 | } 198 | return nil 199 | } 200 | return (key, MappingPropertyHandler(rawPaths: transformer.0, assignmentClosure: assignmentClosure, takeValueClosure: takeValueClosure)) 201 | } 202 | 203 | infix operator <<< : AssignmentPrecedence 204 | 205 | public func <<< (mapper: HelpingMapper, mapping: CustomMappingKeyValueTuple) { 206 | mapper.addCustomMapping(key: mapping.0, mappingInfo: mapping.1) 207 | } 208 | 209 | public func <<< (mapper: HelpingMapper, mappings: [CustomMappingKeyValueTuple]) { 210 | mappings.forEach { (mapping) in 211 | mapper.addCustomMapping(key: mapping.0, mappingInfo: mapping.1) 212 | } 213 | } 214 | 215 | infix operator >>> : AssignmentPrecedence 216 | 217 | public func >>> (mapper: HelpingMapper, property: inout T) { 218 | mapper._exclude(property: &property) 219 | } 220 | -------------------------------------------------------------------------------- /Tests/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 <<< 49 | self.name <-- "json_name" 50 | 51 | // specify converting method 52 | mapper <<< 53 | self.id <-- TransformOf(fromJSON: { (rawValue) -> String? in 54 | if let str = rawValue { 55 | return "json_" + str 56 | } 57 | return nil 58 | }, toJSON: { (id) -> String? in 59 | return id 60 | }) 61 | 62 | mapper <<< 63 | self.height <-- ("json_height", TransformOf(fromJSON: { (rawValue) -> Int? in 64 | if let _str = rawValue { 65 | return Int(_str) ?? 0 66 | } 67 | return nil 68 | }, toJSON: { (height) -> String? in 69 | if let _height = height { 70 | return "\(_height)" 71 | } 72 | return nil 73 | })) 74 | } 75 | } 76 | 77 | let jsonString = "{\"json_name\":\"Bob\",\"id\":\"12345\",\"json_height\":\"180\"}" 78 | let a = A.deserialize(from: jsonString)! 79 | XCTAssert(a.name == "Bob") 80 | XCTAssert(a.id == "json_12345") 81 | XCTAssert(a.height == 180) 82 | } 83 | 84 | func testMultipleNamesMapToOneProperty() { 85 | 86 | class A: HandyJSON { 87 | var var1: String? 88 | var var2: String? 89 | var var3: String? 90 | 91 | required init() {} 92 | 93 | func mapping(mapper: HelpingMapper) { 94 | mapper <<< 95 | self.var1 <-- ["var1_v1", "var1_v2", "var1_v3"] 96 | 97 | mapper <<< 98 | self.var2 <-- (["var2_v1", "var2_v2", "var2_v3"], TransformOf(fromJSON: { (rawStr) -> String? in 99 | return rawStr 100 | }, toJSON: { (srcStr) -> String? in 101 | return srcStr 102 | })) 103 | 104 | mapper <<< 105 | self.var3 <-- ["var3_v1"] 106 | 107 | } 108 | } 109 | 110 | var jsonString = "{\"var1_v1\":\"var1_value\",\"var2_v1\":\"var2_value\",\"var3_v1\":\"var3_value\"}" 111 | var a = A.deserialize(from: jsonString)! 112 | XCTAssert(a.var1 == "var1_value") 113 | XCTAssert(a.var2 == "var2_value") 114 | XCTAssert(a.var3 == "var3_value") 115 | let toJson = a.toJSON()! 116 | XCTAssert(toJson["var1_v1"] as? String == "var1_value") 117 | XCTAssert(toJson["var1_v2"] as? String == nil) 118 | XCTAssert(toJson["var1_v3"] as? String == nil) 119 | XCTAssert(toJson["var2_v1"] as? String == "var2_value") 120 | XCTAssert(toJson["var2_v2"] as? String == nil) 121 | XCTAssert(toJson["var2_v3"] as? String == nil) 122 | XCTAssert(toJson["var3_v1"] as? String == "var3_value") 123 | 124 | jsonString = "{\"var1_v2\":\"var1_value\",\"var2_v2\":\"var2_value\",\"var3_v2\":\"var3_value\"}" 125 | a = A.deserialize(from: jsonString)! 126 | XCTAssert(a.var1 == "var1_value") 127 | XCTAssert(a.var2 == "var2_value") 128 | XCTAssert(a.var3 == nil) 129 | 130 | jsonString = "{\"var1\":\"var1_value\",\"var2\":\"var2_value\",\"var3\":\"var3_value\"}" 131 | a = A.deserialize(from: jsonString)! 132 | XCTAssert(a.var1 == nil) 133 | XCTAssert(a.var2 == nil) 134 | XCTAssert(a.var3 == nil) 135 | } 136 | 137 | func testClassMapping() { 138 | 139 | class A: HandyJSON { 140 | var name: String? 141 | var id: String? 142 | var height: Int? 143 | 144 | required init() {} 145 | 146 | func mapping(mapper: HelpingMapper) { 147 | // specify json field name 148 | mapper <<< 149 | self.name <-- "json_name" 150 | 151 | // specify converting method 152 | mapper <<< 153 | self.id <-- TransformOf(fromJSON: { (rawStr) -> String? in 154 | if let _str = rawStr { 155 | return "json_" + _str 156 | } 157 | return nil 158 | }, toJSON: { (srcStr) -> String? in 159 | return srcStr 160 | }) 161 | 162 | // specify both 163 | mapper <<< 164 | self.height <-- ("json_height", TransformOf(fromJSON: { (rawInt) -> Int? in 165 | if let _int = rawInt { 166 | return _int / 100 167 | } 168 | return nil 169 | }, toJSON: { (srcInt) -> Int? in 170 | if let _int = srcInt { 171 | return _int * 100 172 | } 173 | return nil 174 | })) 175 | } 176 | } 177 | 178 | let jsonString = "{\"json_name\":\"Bob\",\"id\":\"12345\",\"json_height\":18000}" 179 | let a = A.deserialize(from: jsonString)! 180 | XCTAssert(a.name == "Bob") 181 | XCTAssert(a.id == "json_12345") 182 | XCTAssert(a.height == 180) 183 | } 184 | 185 | func testExlucdePropertyForClass() { 186 | struct NotHandyJSON { 187 | var empty: String? 188 | } 189 | class A: HandyJSON { 190 | var notHandyJSONProperty: NotHandyJSON? 191 | var name: String? 192 | var id: String? 193 | var height: Int? 194 | 195 | required init() {} 196 | 197 | func mapping(mapper: HelpingMapper) { 198 | mapper >>> self.notHandyJSONProperty 199 | mapper >>> self.name 200 | } 201 | } 202 | let jsonString = "{\"name\":\"Bob\",\"id\":\"12345\",\"height\":180}" 203 | let a = A.deserialize(from: jsonString)! 204 | XCTAssert(a.name == nil) 205 | XCTAssert(a.id == "12345") 206 | XCTAssert(a.height == 180) 207 | } 208 | 209 | func testExlucdePropertyForStruct() { 210 | class NotHandyJSON { 211 | var empty: String? 212 | } 213 | struct A: HandyJSON { 214 | var name: String? 215 | var id: String? 216 | var height: Int? 217 | var notHandyJSONProperty: NotHandyJSON? 218 | 219 | mutating func mapping(mapper: HelpingMapper) { 220 | mapper >>> self.notHandyJSONProperty 221 | mapper >>> name 222 | } 223 | } 224 | let jsonString = "{\"name\":\"Bob\",\"id\":\"12345\",\"height\":180, \"notHandyJSONProperty\":\"value\"}" 225 | let a = A.deserialize(from: jsonString)! 226 | XCTAssert(a.name == nil) 227 | XCTAssert(a.id == "12345") 228 | XCTAssert(a.height == 180) 229 | } 230 | 231 | func testAfterMappingForClass() { 232 | class A: HandyJSON { 233 | var name: String? 234 | var upperName: String? 235 | required init() {} 236 | 237 | func afterMap() { 238 | upperName = name?.uppercased() 239 | } 240 | } 241 | 242 | let jsonString = "{\"name\":\"HandyJson\"}" 243 | let a = A.deserialize(from: jsonString)! 244 | XCTAssertEqual(a.upperName, "HANDYJSON") 245 | } 246 | 247 | func testAfterMappingForStruct() { 248 | struct A: HandyJSON { 249 | var name: String? 250 | var upperName: String? 251 | 252 | mutating func afterMap() { 253 | upperName = name?.uppercased() 254 | } 255 | } 256 | 257 | let jsonString = "{\"name\":\"HandyJson\"}" 258 | let a = A.deserialize(from: jsonString)! 259 | XCTAssertEqual(a.upperName, "HANDYJSON") 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Source/ExtendCustomModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtendCustomType.swift 3 | // HandyJSON 4 | // 5 | // Created by zhouzhuo on 16/07/2017. 6 | // Copyright © 2017 aliyun. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol _ExtendCustomModelType: _Transformable { 12 | init() 13 | mutating func willStartMapping() 14 | mutating func mapping(mapper: HelpingMapper) 15 | mutating func didFinishMapping() 16 | } 17 | 18 | extension _ExtendCustomModelType { 19 | 20 | public mutating func willStartMapping() {} 21 | public mutating func mapping(mapper: HelpingMapper) {} 22 | public mutating func didFinishMapping() {} 23 | } 24 | 25 | fileprivate func convertKeyIfNeeded(dict: [String: Any]) -> [String: Any] { 26 | if HandyJSONConfiguration.deserializeOptions.contains(.caseInsensitive) { 27 | var newDict = [String: Any]() 28 | dict.forEach({ (kvPair) in 29 | let (key, value) = kvPair 30 | newDict[key.lowercased()] = value 31 | }) 32 | return newDict 33 | } 34 | return dict 35 | } 36 | 37 | fileprivate func getRawValueFrom(dict: [String: Any], property: PropertyInfo, mapper: HelpingMapper) -> Any? { 38 | let address = Int(bitPattern: property.address) 39 | if let mappingHandler = mapper.getMappingHandler(key: address) { 40 | if let mappingPaths = mappingHandler.mappingPaths, mappingPaths.count > 0 { 41 | for mappingPath in mappingPaths { 42 | if let _value = dict.findValueBy(path: mappingPath) { 43 | return _value 44 | } 45 | } 46 | return nil 47 | } 48 | } 49 | if HandyJSONConfiguration.deserializeOptions.contains(.caseInsensitive) { 50 | return dict[property.key.lowercased()] 51 | } 52 | return dict[property.key] 53 | } 54 | 55 | fileprivate func convertValue(rawValue: Any, property: PropertyInfo, mapper: HelpingMapper) -> Any? { 56 | if rawValue is NSNull { return nil } 57 | if let mappingHandler = mapper.getMappingHandler(key: Int(bitPattern: property.address)), let transformer = mappingHandler.assignmentClosure { 58 | return transformer(rawValue) 59 | } 60 | if let transformableType = property.type as? _Transformable.Type { 61 | return transformableType.transform(from: rawValue) 62 | } else { 63 | return extensions(of: property.type).takeValue(from: rawValue) 64 | } 65 | } 66 | 67 | fileprivate func assignProperty(convertedValue: Any, instance: _ExtendCustomModelType, property: PropertyInfo) { 68 | if property.bridged { 69 | (instance as! NSObject).setValue(convertedValue, forKey: property.key) 70 | } else { 71 | extensions(of: property.type).write(convertedValue, to: property.address) 72 | } 73 | } 74 | 75 | fileprivate func readAllChildrenFrom(mirror: Mirror) -> [(String, Any)] { 76 | var children = [(label: String?, value: Any)]() 77 | children += mirror.children 78 | 79 | var currentMirror = mirror 80 | while let superclassChildren = currentMirror.superclassMirror?.children { 81 | children += superclassChildren 82 | currentMirror = currentMirror.superclassMirror! 83 | } 84 | var result = [(String, Any)]() 85 | children.forEach { (child) in 86 | if let _label = child.label { 87 | result.append((_label, child.value)) 88 | } 89 | } 90 | return result 91 | } 92 | 93 | fileprivate func merge(children: [(String, Any)], propertyInfos: [PropertyInfo]) -> [String: (Any, PropertyInfo?)] { 94 | var infoDict = [String: PropertyInfo]() 95 | propertyInfos.forEach { (info) in 96 | infoDict[info.key] = info 97 | } 98 | 99 | var result = [String: (Any, PropertyInfo?)]() 100 | children.forEach { (child) in 101 | result[child.0] = (child.1, infoDict[child.0]) 102 | } 103 | return result 104 | } 105 | 106 | // this's a workaround before https://bugs.swift.org/browse/SR-5223 fixed 107 | extension NSObject { 108 | static func createInstance() -> NSObject { 109 | return self.init() 110 | } 111 | } 112 | 113 | extension _ExtendCustomModelType { 114 | 115 | static func _transform(from object: Any) -> Self? { 116 | if let dict = object as? [String: Any] { 117 | // nested object, transform recursively 118 | return self._transform(dict: dict) as? Self 119 | } 120 | return nil 121 | } 122 | 123 | static func _transform(dict: [String: Any]) -> _ExtendCustomModelType? { 124 | 125 | var instance: Self 126 | if let _nsType = Self.self as? NSObject.Type { 127 | instance = _nsType.createInstance() as! Self 128 | } else { 129 | instance = Self.init() 130 | } 131 | instance.willStartMapping() 132 | _transform(dict: dict, to: &instance) 133 | instance.didFinishMapping() 134 | return instance 135 | } 136 | 137 | static func _transform(dict: [String: Any], to instance: inout Self) { 138 | guard let properties = getProperties(forType: Self.self) else { 139 | InternalLogger.logDebug("Failed when try to get properties from type: \(type(of: Self.self))") 140 | return 141 | } 142 | 143 | // do user-specified mapping first 144 | let mapper = HelpingMapper() 145 | instance.mapping(mapper: mapper) 146 | 147 | // get head addr 148 | let rawPointer = instance.headPointer() 149 | InternalLogger.logVerbose("instance start at: ", Int(bitPattern: rawPointer)) 150 | 151 | // process dictionary 152 | let _dict = convertKeyIfNeeded(dict: dict) 153 | 154 | let instanceIsNsObject = instance.isNSObjectType() 155 | let bridgedPropertyList = instance.getBridgedPropertyList() 156 | 157 | for property in properties { 158 | let isBridgedProperty = instanceIsNsObject && bridgedPropertyList.contains(property.key) 159 | 160 | let propAddr = rawPointer.advanced(by: property.offset) 161 | InternalLogger.logVerbose(property.key, "address at: ", Int(bitPattern: propAddr)) 162 | if mapper.propertyExcluded(key: Int(bitPattern: propAddr)) { 163 | InternalLogger.logDebug("Exclude property: \(property.key)") 164 | continue 165 | } 166 | 167 | let propertyDetail = PropertyInfo(key: property.key, type: property.type, address: propAddr, bridged: isBridgedProperty) 168 | InternalLogger.logVerbose("field: ", property.key, " offset: ", property.offset, " isBridgeProperty: ", isBridgedProperty) 169 | 170 | if let rawValue = getRawValueFrom(dict: _dict, property: propertyDetail, mapper: mapper) { 171 | if let convertedValue = convertValue(rawValue: rawValue, property: propertyDetail, mapper: mapper) { 172 | assignProperty(convertedValue: convertedValue, instance: instance, property: propertyDetail) 173 | continue 174 | } 175 | } 176 | InternalLogger.logDebug("Property: \(property.key) hasn't been written in") 177 | } 178 | } 179 | } 180 | 181 | extension _ExtendCustomModelType { 182 | 183 | func _plainValue() -> Any? { 184 | return Self._serializeAny(object: self) 185 | } 186 | 187 | static func _serializeAny(object: _Transformable) -> Any? { 188 | 189 | let mirror = Mirror(reflecting: object) 190 | 191 | guard let displayStyle = mirror.displayStyle else { 192 | return object.plainValue() 193 | } 194 | 195 | // after filtered by protocols above, now we expect the type is pure struct/class 196 | switch displayStyle { 197 | case .class, .struct: 198 | let mapper = HelpingMapper() 199 | // do user-specified mapping first 200 | if !(object is _ExtendCustomModelType) { 201 | InternalLogger.logDebug("This model of type: \(type(of: object)) is not mappable but is class/struct type") 202 | return object 203 | } 204 | 205 | let children = readAllChildrenFrom(mirror: mirror) 206 | 207 | guard let properties = getProperties(forType: type(of: object)) else { 208 | InternalLogger.logError("Can not get properties info for type: \(type(of: object))") 209 | return nil 210 | } 211 | 212 | var mutableObject = object as! _ExtendCustomModelType 213 | let instanceIsNsObject = mutableObject.isNSObjectType() 214 | let head = mutableObject.headPointer() 215 | let bridgedProperty = mutableObject.getBridgedPropertyList() 216 | let propertyInfos = properties.map({ (desc) -> PropertyInfo in 217 | return PropertyInfo(key: desc.key, type: desc.type, address: head.advanced(by: desc.offset), 218 | bridged: instanceIsNsObject && bridgedProperty.contains(desc.key)) 219 | }) 220 | 221 | mutableObject.mapping(mapper: mapper) 222 | 223 | let requiredInfo = merge(children: children, propertyInfos: propertyInfos) 224 | 225 | return _serializeModelObject(instance: mutableObject, properties: requiredInfo, mapper: mapper) as Any 226 | default: 227 | return object.plainValue() 228 | } 229 | } 230 | 231 | static func _serializeModelObject(instance: _ExtendCustomModelType, properties: [String: (Any, PropertyInfo?)], mapper: HelpingMapper) -> [String: Any] { 232 | 233 | var dict = [String: Any]() 234 | for (key, property) in properties { 235 | var realKey = key 236 | var realValue = property.0 237 | 238 | if let info = property.1 { 239 | if info.bridged, let _value = (instance as! NSObject).value(forKey: key) { 240 | realValue = _value 241 | } 242 | 243 | if mapper.propertyExcluded(key: Int(bitPattern: info.address)) { 244 | continue 245 | } 246 | 247 | if let mappingHandler = mapper.getMappingHandler(key: Int(bitPattern: info.address)) { 248 | // if specific key is set, replace the label 249 | if let mappingPaths = mappingHandler.mappingPaths, mappingPaths.count > 0 { 250 | // take the first path, last segment if more than one 251 | realKey = mappingPaths[0].segments.last! 252 | } 253 | 254 | if let transformer = mappingHandler.takeValueClosure { 255 | if let _transformedValue = transformer(realValue) { 256 | dict[realKey] = _transformedValue 257 | } 258 | continue 259 | } 260 | } 261 | } 262 | 263 | if let typedValue = realValue as? _Transformable { 264 | if let result = self._serializeAny(object: typedValue) { 265 | dict[realKey] = result 266 | continue 267 | } 268 | } 269 | 270 | InternalLogger.logDebug("The value for key: \(key) is not transformable type") 271 | } 272 | return dict 273 | } 274 | } 275 | 276 | --------------------------------------------------------------------------------