OSDN Git Service

Swift4.0にコンバートした
[kcd/KCD.git] / KCD / JSONMapper.swift
1 //
2 //  JSONMapper.swift
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2017/02/22.
6 //  Copyright © 2017年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10 import SwiftyJSON
11
12 struct MappingConfiguration<T: NSManagedObject> {
13     
14     let entity: Entity<T>
15     let dataKeys: [String]
16     let primaryKeys: [String]
17     let editorStore: CoreDataAccessor
18     let ignoreKeys: [String]
19     
20     init(entity: Entity<T>,
21          dataKeys: [String] = ["api_data"],
22          primaryKeys: [String] = ["id"],
23          editorStore: CoreDataAccessor,
24          ignoreKeys: [String] = []) {
25         
26         self.entity = entity
27         self.dataKeys = dataKeys
28         self.primaryKeys = primaryKeys
29         self.editorStore = editorStore
30         self.ignoreKeys = ignoreKeys
31     }
32 }
33
34 protocol JSONMapper {
35     
36     associatedtype ObjectType: NSManagedObject
37     
38     init(_ apiResponse: APIResponse)
39     
40     var apiResponse: APIResponse { get }
41     var configuration: MappingConfiguration<ObjectType> { get }
42     
43     func registerElement(_ element: JSON, to object: ObjectType)
44     func commit()
45     func beginRegister(_ object: ObjectType)
46     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> Bool
47     func finishOperating()
48 }
49
50 extension String {
51     
52     func keyByDeletingPrefix() -> String {
53         
54         if self.characters.count < 5 { return self }
55         
56 /* MARK: CONVERT */        return String(self[index(startIndex, offsetBy: 4)...])
57     }
58 }
59
60 extension JSONMapper {
61     
62     var data: JSON { return apiResponse.json[configuration.dataKeys] }
63     
64     private func isEqual(_ lhs: AnyObject?, _ rhs: AnyObject?) -> Bool {
65         
66         if lhs == nil, rhs == nil { return true }
67         if let lhs = lhs, let rhs = rhs { return lhs.isEqual(rhs) }
68         
69         return false
70     }
71     
72     func setValueIfNeeded(_ value: JSON, to object: ObjectType, forKey key: String) {
73         
74         var validValue = value.object as AnyObject?
75         do {
76             
77             try object.validateValue(&validValue, forKey: key)
78             
79         } catch {
80             
81             return
82             
83         }
84         
85         let old = object.value(forKey: key)
86         if !isEqual(old as AnyObject?, validValue) {
87             
88             object.notifyChangeValue(forKey: key) {
89                 
90                 object.setValue(validValue, forKey: key)
91                 
92             }
93         }
94     }
95     
96     func registerElement(_ element: JSON, to object: ObjectType) {
97         
98         beginRegister(object)
99         element.forEach { (key, value) in
100             
101             if configuration.ignoreKeys.contains(key) { return }
102             if handleExtraValue(value, forKey: key, to: object) { return }
103             
104             switch value.type {
105             case .array:
106                 value.array?.enumerated().forEach {
107                     
108                     let newKey = "\(key)_\($0.offset)"
109                     setValueIfNeeded($0.element, to: object, forKey: newKey)
110                 }
111                 
112             case .dictionary:
113                 value.forEach { (subKey: String, subValue) in
114                     
115                     let newKey = "\(key)_D_\(subKey.keyByDeletingPrefix())"
116                     setValueIfNeeded(subValue, to: object, forKey: newKey)
117                 }
118             default:
119                 
120                 setValueIfNeeded(value, to: object, forKey: key)
121             }
122         }
123     }
124     
125     private var sortDescriptors: [NSSortDescriptor] {
126         
127         return configuration.primaryKeys.map { NSSortDescriptor(key: $0, ascending: true) }
128     }
129     
130     private func objectSearch(_ objects: [ObjectType], _ element: JSON) -> ObjectType? {
131         
132         let keyPiar = configuration.primaryKeys.map { (key: $0, apiKey: "api_\($0)") }
133         
134         return objects.binarySearch {
135             
136             // TODO: replace to forEach
137             for piar in keyPiar {
138                 
139                 guard let v1 = $0.value(forKey: piar.key)
140                     else { return .orderedAscending }
141                 
142                 if element[piar.apiKey].type == .null { return .orderedDescending }
143                 
144                 let v2 = element[piar.apiKey].object
145                 
146                 return (v1 as AnyObject).compare(v2)
147             }
148             
149             return .orderedDescending
150         }
151     }
152     
153     func commit() {
154         
155         let store = configuration.editorStore
156         
157         guard let objects = try? store.objects(of: configuration.entity, sortDescriptors: sortDescriptors)
158             else { return print("Can not get entity named \(configuration.entity.name)") }
159         
160         let list = (data.type == .array ? data.arrayValue : [data])
161         list.forEach {
162             
163             if let object = objectSearch(objects, $0) {
164                 
165                 registerElement($0, to: object)
166                 
167             } else if let new = store.insertNewObject(for: configuration.entity) {
168                 
169                 registerElement($0, to: new)
170                 
171             } else {
172                 
173                 fatalError("Can not get entity named \(configuration.entity.name)")
174                 
175             }
176         }
177         
178         finishOperating()
179         store.save()
180     }
181     
182     func beginRegister(_ object: ObjectType) {}
183     
184     func handleExtraValue(_ value: JSON, forKey key: String, to object: ObjectType) -> Bool {
185         
186         return false
187     }
188     
189     func finishOperating() {}
190 }