Skip to content

Commit 534c8a7

Browse files
authored
feat: implementing OptimizelyJSON (#315)
* Initial commit. * Unit tests implemented. * indentation fixed. * 1. Recommended changes made. 2. Implemented tests for objc conversion. 3. Added GetValue method for Objc. * Verifying build issues. * Addressed review comments. * Addressed review comments.
1 parent 7887ca2 commit 534c8a7

File tree

7 files changed

+726
-14
lines changed

7 files changed

+726
-14
lines changed

OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 64 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/****************************************************************************
2+
* Copyright 2020, Optimizely, Inc. and contributors *
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+
import Foundation
18+
19+
extension OptimizelyJSON {
20+
21+
@available(swift, obsoleted: 1.0)
22+
@objc(initWithPayload:)
23+
convenience init?(p: String) {
24+
self.init(payload: p)
25+
}
26+
27+
@available(swift, obsoleted: 1.0)
28+
@objc(initWithMap:)
29+
convenience init?(m: [String: Any]) {
30+
self.init(map: m)
31+
}
32+
33+
@available(swift, obsoleted: 1.0)
34+
@objc(toString)
35+
/// - Returns: The string representation of json
36+
public func objcToString() -> String? {
37+
return self.toString()
38+
}
39+
40+
@available(swift, obsoleted: 1.0)
41+
@objc(toMap)
42+
/// - Returns: The json dictionary
43+
public func objcToMap() -> [String: Any] {
44+
return self.toMap()
45+
}
46+
47+
@available(swift, obsoleted: 1.0)
48+
@objc(getValueWithJsonPath:schema:)
49+
/// Populates the schema passed by the user
50+
///
51+
/// - Parameters:
52+
/// - jsonPath: Key path for the value.
53+
/// - schema: Schema to populate.
54+
/// - Returns: true if value decoded successfully
55+
public func objcGetValue(jsonPath: String, schema: UnsafeMutablePointer<AnyObject>) -> Bool {
56+
return self.getValue(jsonPath: jsonPath, schema: &schema.pointee)
57+
}
58+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/****************************************************************************
2+
* Copyright 2020, Optimizely, Inc. and contributors *
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+
import Foundation
18+
19+
public class OptimizelyJSON: NSObject {
20+
21+
private lazy var logger = OPTLoggerFactory.getLogger()
22+
private typealias SchemaHandler = (Any) -> Bool
23+
var payload: String?
24+
var map = [String: Any]()
25+
26+
// MARK: - Init
27+
28+
init?(payload: String) {
29+
do {
30+
guard let data = payload.data(using: .utf8),
31+
let dict = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
32+
return nil
33+
}
34+
self.map = dict
35+
self.payload = payload
36+
} catch {
37+
return nil
38+
}
39+
}
40+
41+
init?(map: [String: Any]) {
42+
if !JSONSerialization.isValidJSONObject(map) {
43+
return nil
44+
}
45+
self.map = map
46+
}
47+
48+
// MARK: - OptimizelyJSON Implementation
49+
50+
/// - Returns: The string representation of json
51+
public func toString() -> String? {
52+
guard let payload = self.payload else {
53+
guard let jsonData = try? JSONSerialization.data(withJSONObject: map,
54+
options: []),
55+
let jsonString = String(data: jsonData, encoding: .utf8) else {
56+
logger.e(.failedToConvertMapToString)
57+
return nil
58+
}
59+
self.payload = jsonString
60+
return jsonString
61+
}
62+
return payload
63+
}
64+
65+
/// - Returns: The json dictionary
66+
public func toMap() -> [String: Any] {
67+
return map
68+
}
69+
70+
/// Populates the decodable schema passed by the user
71+
///
72+
/// - Parameters:
73+
/// - jsonPath: Key path for the value.
74+
/// - schema: Decodable schema to populate.
75+
/// - Returns: true if value decoded successfully
76+
public func getValue<T: Decodable>(jsonPath: String?, schema: inout T) -> Bool {
77+
func populateDecodableSchema(value: Any) -> Bool {
78+
guard JSONSerialization.isValidJSONObject(value) else {
79+
// Try and assign value directly to schema
80+
if let v = value as? T {
81+
schema = v
82+
return true
83+
}
84+
logger.e(.failedToAssignValueToSchema)
85+
return false
86+
}
87+
// Try to decode value into schema
88+
guard let jsonData = try? JSONSerialization.data(withJSONObject: value, options: []),
89+
let decodedValue = try? JSONDecoder().decode(T.self, from: jsonData) else {
90+
logger.e(.failedToAssignValueToSchema)
91+
return false
92+
}
93+
schema = decodedValue
94+
return true
95+
}
96+
return getValue(jsonPath: jsonPath, schemaHandler: populateDecodableSchema(value:))
97+
}
98+
99+
/// Populates the schema passed by the user
100+
///
101+
/// - Parameters:
102+
/// - jsonPath: Key path for the value.
103+
/// - schema: Schema to populate.
104+
/// - Returns: true if value decoded successfully
105+
public func getValue<T>(jsonPath: String?, schema: inout T) -> Bool {
106+
func populateSchema(value: Any) -> Bool {
107+
guard let v = value as? T else {
108+
self.logger.e(.failedToAssignValueToSchema)
109+
return false
110+
}
111+
schema = v
112+
return true
113+
}
114+
return getValue(jsonPath: jsonPath, schemaHandler: populateSchema(value:))
115+
}
116+
117+
private func getValue(jsonPath: String?, schemaHandler: SchemaHandler) -> Bool {
118+
119+
guard let path = jsonPath, !path.isEmpty else {
120+
// Populate the whole schema
121+
return schemaHandler(map)
122+
}
123+
124+
let pathArray = path.components(separatedBy: ".")
125+
let lastIndex = pathArray.count - 1
126+
127+
var internalMap = map
128+
for (index, key) in pathArray.enumerated() {
129+
guard let value = internalMap[key] else {
130+
self.logger.e(.valueForKeyNotFound(key))
131+
return false
132+
}
133+
if let dict = value as? [String: Any] {
134+
internalMap = dict
135+
}
136+
if index == lastIndex {
137+
return schemaHandler(value)
138+
}
139+
}
140+
return false
141+
}
142+
}

Sources/Utils/LogMessage.swift

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
/****************************************************************************
2-
* Copyright 2019, Optimizely, Inc. and contributors *
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-
***************************************************************************/
2+
* Copyright 2019-2020, Optimizely, Inc. and contributors *
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+
***************************************************************************/
1616

1717
import Foundation
1818

@@ -66,6 +66,9 @@ enum LogMessage {
6666
case unrecognizedAttribute(_ key: String)
6767
case eventBatchFailed
6868
case eventSendRetyFailed(_ count: Int)
69+
case failedToConvertMapToString
70+
case failedToAssignValueToSchema
71+
case valueForKeyNotFound(_ key: String)
6972
}
7073

7174
extension LogMessage: CustomStringConvertible {
@@ -122,6 +125,9 @@ extension LogMessage: CustomStringConvertible {
122125
case .unrecognizedAttribute(let key): message = "Unrecognized attribute (\(key)) provided. Pruning before sending event to Optimizely."
123126
case .eventBatchFailed: message = "Failed to batch events"
124127
case .eventSendRetyFailed(let count): message = "Event dispatch retries failed (\(count)) times"
128+
case .failedToConvertMapToString: message = "Provided map could not be converted to string."
129+
case .failedToAssignValueToSchema: message = "Value for path could not be assigned to provided schema."
130+
case .valueForKeyNotFound(let key): message = "Value for JSON key (\(key)) not found."
125131
}
126132

127133
return message

0 commit comments

Comments
 (0)