Skip to content

Commit 5807ed1

Browse files
authored
Replaced SwiftyJSON with Codable for JSON Parsing (BuilderIO#39)
* Removed Swifty JSON dependency and replaced with the inbuild codable JSON decoder provided in swift * code clean up * removed swifty json from packages resolved * code cleanup
1 parent 4deab4e commit 5807ed1

File tree

12 files changed

+181
-66
lines changed

12 files changed

+181
-66
lines changed

Package.resolved

Lines changed: 0 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ let package = Package(
2020

2121
// --- Dependencies ---
2222
dependencies: [
23-
.package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.1"),
2423
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.17.0"),
2524
.package(url: "https://github.com/WeTransfer/Mocker.git", .upToNextMajor(from: "3.0.0")),
2625
],
@@ -30,9 +29,6 @@ let package = Package(
3029
// Main library target
3130
.target(
3231
name: "BuilderIO",
33-
dependencies: [
34-
"SwiftyJSON",
35-
],
3632
resources: [
3733
.process("Resources/Fonts")
3834
]
@@ -51,4 +47,4 @@ let package = Package(
5147
]
5248
),
5349
]
54-
)
50+
)

Sources/BuilderIO/Components/BuilderBlock.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ struct BuilderBlock: View {
5454
// Only checking links for now, can be expanded to cover events in the future
5555
let isTappable =
5656
component?.name == BuilderComponentType.coreButton.rawValue
57-
|| !(component?.options?["Link"].isEmpty ?? true) || !(child.linkUrl?.isEmpty ?? true)
57+
|| !(component?.options?.dictionaryValue?["Link"]?.stringValue?.isEmpty ?? true)
58+
|| !(child.linkUrl?.isEmpty ?? true)
5859

5960
let builderAction: BuilderAction? =
6061
(isTappable)

Sources/BuilderIO/Components/BuilderColumns.swift

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Foundation
22
import SwiftUI
3-
import SwiftyJSON
43

54
struct BuilderColumns: BuilderViewProtocol {
65
static let componentType: BuilderComponentType = .columns
@@ -15,23 +14,45 @@ struct BuilderColumns: BuilderViewProtocol {
1514
init(block: BuilderBlockModel) {
1615
self.block = block
1716

18-
if let jsonString = block.component?.options?["columns"].rawString(),
19-
let jsonData = jsonString.data(using: .utf8)
17+
self.columns = []
18+
19+
if let columnsAnyCodableArray = block.component?.options?
20+
.dictionaryValue?["columns"]?
21+
.arrayValue
2022
{
23+
2124
let decoder = JSONDecoder()
22-
do {
23-
self.columns = try decoder.decode([BuilderContentData].self, from: jsonData)
24-
} catch {
25-
self.columns = []
25+
var decodedColumns: [BuilderContentData] = []
26+
27+
// Iterate through each AnyCodable element in the array
28+
for anyCodableElement in columnsAnyCodableArray {
29+
do {
30+
// Convert the AnyCodable element back into Data
31+
// (This is necessary because JSONDecoder works with Data)
32+
let elementData = try JSONEncoder().encode(anyCodableElement)
33+
34+
// Decode that Data into a BuilderContentData instance
35+
let column = try decoder.decode(BuilderContentData.self, from: elementData)
36+
decodedColumns.append(column)
37+
} catch {
38+
// Handle error for a specific element if it can't be decoded
39+
print("Error decoding individual BuilderContentData from AnyCodable element: \(error)")
40+
// You might choose to append a default empty BuilderContentData,
41+
// or simply skip this element, as we are doing here.
42+
}
2643
}
44+
self.columns = decodedColumns
45+
2746
} else {
28-
self.columns = []
47+
print("Could not find or access 'columns' array in component options.")
2948
}
3049

31-
self.space = block.component?.options?["space"].doubleValue ?? 0
32-
self.stackColumns = !(block.component?.options?["stackColumnsAt"] == "never" ?? false)
50+
self.space = block.component?.options?.dictionaryValue?["space"]?.doubleValue ?? 0
51+
self.stackColumns =
52+
!((block.component?.options?.dictionaryValue?["stackColumnsAt"]?.stringValue == "never")
53+
?? false)
3354
self.reverseColumnsWhenStacked =
34-
block.component?.options?["reverseColumnsWhenStacked"].boolValue ?? false
55+
block.component?.options?.dictionaryValue?["reverseColumnsWhenStacked"]?.boolValue ?? false
3556

3657
}
3758

Sources/BuilderIO/Components/BuilderImage.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ struct BuilderImage: BuilderViewProtocol {
1616

1717
init(block: BuilderBlockModel) {
1818
self.block = block
19-
self.imageURL = URL(string: block.component?.options?["image"].string ?? "")
20-
if let ratio = block.component?.options?["aspectRatio"].float {
19+
self.imageURL = URL(
20+
string: block.component?.options?.dictionaryValue?["image"]?.stringValue ?? "")
21+
if let ratio = block.component?.options?.dictionaryValue?["aspectRatio"]?.doubleValue {
2122
self.aspectRatio = CGFloat(1 / ratio)
2223
}
2324

2425
self.children = block.children
25-
self.contentMode = block.component?.options?["backgroundSize"] == "cover" ? .fill : .fit
26+
self.contentMode =
27+
block.component?.options?.dictionaryValue?["backgroundSize"]?.stringValue == "cover"
28+
? .fill : .fit
2629
self.fitContent =
27-
(block.component?.options?["fitContent"].boolValue ?? false)
30+
(block.component?.options?.dictionaryValue?["fitContent"]?.boolValue ?? false)
2831
&& !(block.children?.isEmpty ?? true)
2932

3033
}

Sources/BuilderIO/Components/BuilderSection.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Foundation
22
import SwiftUI
3-
import SwiftyJSON
43

54
struct BuilderSection: BuilderViewProtocol {
65

@@ -13,10 +12,11 @@ struct BuilderSection: BuilderViewProtocol {
1312
init(block: BuilderBlockModel) {
1413
self.block = block
1514
self.children = block.children
16-
self.lazyLoad = block.component?.options?["lazyLoad"].bool ?? false
15+
self.lazyLoad = block.component?.options?.dictionaryValue?["lazyLoad"]?.boolValue ?? false
1716
self.maxWidth =
18-
block.component?.options?["maxWidth"] != nil
19-
? CGFloat(block.component?.options?["maxWidth"].float ?? .infinity) : nil
17+
block.component?.options?.dictionaryValue?["maxWidth"] != nil
18+
? CGFloat(block.component?.options?.dictionaryValue?["maxWidth"]?.doubleValue ?? .infinity)
19+
: nil
2020
}
2121

2222
var body: some View {

Sources/BuilderIO/Components/BuilderText.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct BuilderText: BuilderViewProtocol {
1111

1212
init(block: BuilderBlockModel) {
1313
self.block = block
14-
self.text = block.component?.options?["text"].string ?? ""
14+
self.text = block.component?.options?.dictionaryValue?["text"]?.stringValue ?? ""
1515
self.responsiveStyles = getFinalStyle(responsiveStyles: block.responsiveStyles)
1616
}
1717

Sources/BuilderIO/EventActions/BuilderActionHandler.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import SwiftUI
2-
import SwiftyJSON
32

43
public typealias BuilderActionHandler = (BuilderAction) -> Void
54

@@ -16,7 +15,8 @@ public class BuilderActionManager: ObservableObject {
1615

1716
public func handleButtonPress(builderAction: BuilderAction) {
1817

19-
var url: String? = builderAction.options?["link"].string ?? builderAction.linkURL
18+
var url: String? =
19+
builderAction.options?.dictionaryValue?["link"]?.stringValue ?? builderAction.linkURL
2020

2121
//<CUSTOM_SCHEME>://<MODEL_NAME>/<PAGE_URL>?<OPTIONAL_PARAMETERS>
2222
//"builderio://page/my-awesome-page
@@ -61,10 +61,12 @@ public class BuilderActionManager: ObservableObject {
6161
public class BuilderAction {
6262
let componentId: String
6363
let linkURL: String?
64-
let options: JSON?
65-
let eventActions: JSON?
64+
let options: AnyCodable?
65+
let eventActions: AnyCodable?
6666

67-
public init(componentId: String, options: JSON?, eventActions: JSON?, linkURL: String? = nil) {
67+
public init(
68+
componentId: String, options: AnyCodable?, eventActions: AnyCodable?, linkURL: String? = nil
69+
) {
6870
self.componentId = componentId
6971
self.options = options
7072
self.eventActions = eventActions
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import Foundation
2+
3+
// --- AnyCodable Type Definition ---
4+
// This enum allows you to represent any valid JSON value and make it Codable.
5+
public enum AnyCodable: Codable {
6+
case int(Int)
7+
case double(Double)
8+
case string(String)
9+
case bool(Bool)
10+
case array([AnyCodable]) // Can contain other AnyCodable values
11+
case dictionary([String: AnyCodable]) // Can contain other AnyCodable values
12+
case null
13+
14+
// MARK: - Decodable
15+
public init(from decoder: Decoder) throws {
16+
let container = try decoder.singleValueContainer()
17+
18+
if let value = try? container.decode(Int.self) {
19+
self = .int(value)
20+
} else if let value = try? container.decode(Double.self) {
21+
self = .double(value)
22+
} else if let value = try? container.decode(String.self) {
23+
self = .string(value)
24+
} else if let value = try? container.decode(Bool.self) {
25+
self = .bool(value)
26+
} else if let value = try? container.decode([AnyCodable].self) {
27+
self = .array(value)
28+
} else if let value = try? container.decode([String: AnyCodable].self) {
29+
self = .dictionary(value)
30+
} else if container.decodeNil() {
31+
self = .null
32+
} else {
33+
throw DecodingError.dataCorrupted(
34+
DecodingError.Context(
35+
codingPath: decoder.codingPath,
36+
debugDescription: "AnyCodable: Unknown type or malformed JSON value.")
37+
)
38+
}
39+
}
40+
41+
// MARK: - Encodable
42+
public func encode(to encoder: Encoder) throws {
43+
var container = encoder.singleValueContainer()
44+
switch self {
45+
case .int(let value):
46+
try container.encode(value)
47+
case .double(let value):
48+
try container.encode(value)
49+
case .string(let value):
50+
try container.encode(value)
51+
case .bool(let value):
52+
try container.encode(value)
53+
case .array(let value):
54+
try container.encode(value)
55+
case .dictionary(let value):
56+
try container.encode(value)
57+
case .null:
58+
try container.encodeNil()
59+
}
60+
}
61+
62+
// MARK: - Convenience for accessing values (optional but highly recommended)
63+
// Add these so you can easily access specific types without a full switch
64+
public var intValue: Int? {
65+
if case .int(let val) = self { return val }
66+
if case .string(let str) = self, let val = Int(str) { return val }
67+
return nil
68+
}
69+
70+
public var doubleValue: Double? {
71+
if case .double(let val) = self { return val }
72+
if case .int(let val) = self { return Double(val) }
73+
if case .string(let str) = self, let val = Double(str) { return val }
74+
return nil
75+
}
76+
77+
public var stringValue: String? {
78+
if case .string(let val) = self { return val }
79+
if case .int(let val) = self { return String(val) }
80+
if case .double(let val) = self { return String(val) }
81+
if case .bool(let val) = self { return String(val) }
82+
return nil
83+
}
84+
85+
public var boolValue: Bool? {
86+
if case .bool(let val) = self { return val }
87+
if case .int(let val) = self { return val != 0 } // 0 = false, non-zero = true
88+
if case .string(let str) = self { return Bool(str) } // "true", "false"
89+
return nil
90+
}
91+
92+
public var arrayValue: [AnyCodable]? {
93+
if case .array(let val) = self { return val }
94+
return nil
95+
}
96+
97+
public var dictionaryValue: [String: AnyCodable]? {
98+
if case .dictionary(let val) = self { return val }
99+
return nil
100+
}
101+
}

Sources/BuilderIO/Schemas/BuilderBlockModel.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import SwiftyJSON
1+
import Foundation // For Codable, Data, etc.
22

33
// Schema for Builder blocks
44
public struct BuilderBlockModel: Codable, Identifiable {
@@ -8,15 +8,16 @@ public struct BuilderBlockModel: Codable, Identifiable {
88
public var children: [BuilderBlockModel]? = []
99
public var component: BuilderBlockComponent? = nil
1010
public var responsiveStyles: BuilderBlockResponsiveStyles? = BuilderBlockResponsiveStyles() // for inner style of the component
11-
public var actions: JSON? = [:]
12-
public var code: JSON? = [:]
13-
public var meta: JSON? = [:]
11+
public var actions: AnyCodable? = nil // Replaced JSON? with AnyCodable?, default to nil
12+
public var code: AnyCodable? = nil // Replaced JSON? with AnyCodable?, default to nil
13+
public var meta: AnyCodable? = nil // Replaced JSON? with AnyCodable?, default to nil
1414
public var linkUrl: String? = nil
15+
1516
}
1617

1718
public struct BuilderBlockComponent: Codable {
1819
public var name: String
19-
public var options: JSON? = [:]
20+
public var options: AnyCodable? = nil // Replaced JSON? with AnyCodable?, default to nil
2021
}
2122

2223
public struct BuilderBlockResponsiveStyles: Codable {

0 commit comments

Comments
 (0)