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