-
Notifications
You must be signed in to change notification settings - Fork 4
JSON parsing and factories #7
Changes from 20 commits
823cb16
6eb292f
8c6bc88
ba91bc7
6ecf8d0
77b4626
df43352
d6c16f6
2a4fd05
be460f6
f524260
26acb59
62ca01d
06b4bcd
d8ff008
98a5b1d
d218084
81a720f
b3cc35b
e030bf4
dadde5d
ae229e1
452a56b
50dcde2
abd0a79
c80a885
57b6d0b
8119b1b
6a9d36a
63a84b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// | ||
// JSONFactory.swift | ||
// Matrioska | ||
// | ||
// Created by Mathias Aichinger on 11/01/2017. | ||
// Copyright © 2017 runtastic. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
/// A JSONObject type | ||
public typealias JSONObject = [String: Any] | ||
|
||
/// An error type for JSONFactory | ||
/// | ||
/// - missing: specifies that a mandatory key is missing | ||
enum JSONFactoryError: Error { | ||
case missing(JSONObject, String) | ||
} | ||
|
||
/// A factory that wraps `Component` builder closures (ViewFactoryBuilder, | ||
/// `WrapperFactoryBuilder` & `ClusterFactoryBuilder`) and uses them to produce `Component`s | ||
public final class JSONFactory { | ||
|
||
private enum Key { | ||
static let type = "type" | ||
static let meta = "meta" | ||
static let children = "children" | ||
} | ||
|
||
/// A factory closure to build a view `Component` | ||
public typealias ViewFactoryBuilder = (ComponentMeta?) -> Component | ||
|
||
/// A factory closure to build a wrapper `Component` | ||
public typealias WrapperFactoryBuilder = (Component, ComponentMeta?) -> Component | ||
|
||
/// A factory closure to build a cluster `Component` | ||
public typealias ClusterFactoryBuilder = ([Component], ComponentMeta?) -> Component | ||
|
||
private var viewFactory: [String: ViewFactoryBuilder] = [:] | ||
private var wrapperFactory: [String: WrapperFactoryBuilder] = [:] | ||
private var clusterFactory: [String: ClusterFactoryBuilder] = [:] | ||
|
||
/// Initialize a new JSONFactory | ||
public init() { | ||
// Empty but needed to be initialized from other modules | ||
} | ||
|
||
/// Registers a new `ViewFactoryBuilder` which will be used when producing the component | ||
/// | ||
/// - Parameters: | ||
/// - type: a string identifying this factory type | ||
/// - factoryBuilder: a `ViewFactoryBuilder` to build a `Component` | ||
public func register(with type: String, | ||
factoryBuilder: @escaping ViewFactoryBuilder) { | ||
viewFactory[type] = factoryBuilder | ||
} | ||
|
||
/// Registers a new `WrapperFactoryBuilder` which will be used when producing the component | ||
/// | ||
/// - Parameters: | ||
/// - type: a string identifying this factory type | ||
/// - factoryBuilder: a `WrapperFactoryBuilder` to build a `Component` | ||
public func register(with type: String, | ||
factoryBuilder: @escaping WrapperFactoryBuilder) { | ||
wrapperFactory[type] = factoryBuilder | ||
} | ||
|
||
/// Registers a new `ClusterFactoryBuilder` which will be used when producing the component | ||
/// | ||
/// - Parameters: | ||
/// - type: a string identifying this factory type | ||
/// - factoryBuilder: a `ClusterFactoryBuilder` to build a `Component` | ||
public func register(with type: String, | ||
factoryBuilder: @escaping ClusterFactoryBuilder) { | ||
clusterFactory[type] = factoryBuilder | ||
} | ||
|
||
/// Produces a `Component` from a given `JSONObject`, which has one mandatory key: `type` | ||
/// | ||
/// - Parameter json: the `JSONObject` to be used | ||
/// - Returns: An optional `Component` | ||
/// - Throws: `JSONFactoryError` when a mandatory key is missing | ||
public func component(from json: JSONObject) throws -> Component? { | ||
guard let type = json[Key.type] as? String else { | ||
throw JSONFactoryError.missing(json, Key.type) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there should be a possibility to register fallback components. So eg. for clusters always fallback to stacks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mentioned this on last meeting and they said we don't include fallback components for now? We could implement it here though. |
||
} | ||
|
||
let meta = json[Key.meta] as? JSONObject | ||
let children = json[Key.children] as? [JSONObject] ?? [] | ||
let componentChildren = try children.flatMap { try component(from: $0) } | ||
var componentResult: Component? | ||
|
||
if let viewFactory = viewFactory[type] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would do the matching differently: let id = ....
if let children = ... { // is optional
// then is a cluster
} else if let child = ... {
// then is a wrapper
} else {
// must be a view
} But this depends by the json specs There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now we would have the same key for |
||
componentResult = viewFactory(meta) | ||
} else if let wrapperFactory = wrapperFactory[type], | ||
let componentChild = componentChildren.first { | ||
componentResult = wrapperFactory(componentChild, meta) | ||
} else if let clusterFactory = clusterFactory[type] { | ||
componentResult = clusterFactory(componentChildren, meta) | ||
} | ||
|
||
return componentResult | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,14 @@ | ||
disabled_rules: | ||
- trailing_whitespace # xcode add it by default, we might enable it and use autocorrect | ||
- function_body_length | ||
- force_cast | ||
- force_try | ||
- force_unwrapping | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why removed all these rules? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the swiftlint for tests so I disabled them only here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. true! |
||
line_length: 200 # goal: 110 | ||
|
||
opt_in_rules: | ||
- empty_count | ||
- force_unwrapping | ||
- private_outlet | ||
- vertical_whitespace | ||
# - missing_docs # broken | ||
- closure_spacing | ||
- conditional_returns_on_newline | ||
- explicit_init | ||
- overridden_super_call | ||
- redundant_nil_coalesing | ||
# - switch_case_on_newline # broken | ||
|
||
type_body_length: | ||
warning: 800 | ||
|
@@ -23,4 +18,4 @@ file_length: | |
warning: 1000 | ||
error: 1500 | ||
|
||
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit) | ||
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
{ | ||
"structure": { | ||
"type": "tabbar", | ||
"meta": { | ||
"default_tab_id": "main_tab" | ||
}, | ||
"children": [ | ||
{ | ||
"type": "stack", | ||
"meta": { | ||
"tab_config": { | ||
"title": { | ||
"key": "history_title", | ||
"type": "localized_string" | ||
}, | ||
"icon_name": "history_tab_icon" | ||
} | ||
}, | ||
"children": [ | ||
{ | ||
"type": "table_view" | ||
}, | ||
{ | ||
"type": "navigation" | ||
} | ||
] | ||
}, | ||
{ | ||
"type": "stack", | ||
"meta": { | ||
"tab_config": { | ||
"title": { | ||
"key": "main_tab_title", | ||
"type": "localized_string" | ||
}, | ||
"iconName": "main_tab_icon" | ||
} | ||
}, | ||
"children": [ | ||
{ | ||
"type": "label" | ||
}, | ||
{ | ||
"type": "stack", | ||
"meta": { | ||
"requires_navigation": "true", | ||
"orientation": "horizontal" | ||
} | ||
}, | ||
{ | ||
"type": "button", | ||
"meta": { | ||
"title": { | ||
"key": "start_button_title", | ||
"type": "localized_string" | ||
} | ||
} | ||
} | ||
] | ||
}, | ||
{ | ||
"type": "navigation", | ||
"children": [ | ||
{ | ||
"type": "label" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"structure": "test" | ||
"test": "test" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// | ||
// JSONReader.swift | ||
// Matrioska | ||
// | ||
// Created by Andreas Thenn on 12/01/2017. | ||
// Copyright © 2017 runtastic. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
@testable import Matrioska | ||
|
||
/// A JSONReader used to convert to JSONObject | ||
final class JSONReader { | ||
|
||
/// Serializes from a given JSON Data into JSONObject | ||
/// | ||
/// - Parameter data: the Data object used in serialization | ||
/// - Returns: an optional serialized JSONObject | ||
/// - Throws: throws an error in case of failure or invalid JSON data | ||
class func jsonObject(from data: Data) throws -> JSONObject? { | ||
let json = try JSONSerialization.jsonObject(with: data) as? JSONObject | ||
|
||
return json | ||
} | ||
|
||
/// Serializes from a given JSON file into JSONObject | ||
/// | ||
/// - Parameters: | ||
/// - jsonFilename: the file name | ||
/// - bundle: the bundle where the file is located | ||
/// - Returns: an optional serialized JSONObject | ||
/// - Throws: throws an error in case of failure or invalid JSON data | ||
class func jsonObject(from jsonFilename: String, bundle: Bundle = .main) throws -> JSONObject? { | ||
guard let filePath = bundle.path(forResource: jsonFilename, ofType: "json") else { | ||
return nil | ||
} | ||
|
||
let url = URL(fileURLWithPath: filePath) | ||
|
||
return try jsonObject(from: Data(contentsOf: url, options: .uncached)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe setup buddybuild quickly so we can make sure to not break the example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (wanted to see how it works), I created a runtastic team on buddybuild. Will invite you guys. Btw I think buddybuild also supports libraries now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nevermind that's only for payed plans.