ObjectUI/Sources/ObjectUI/Data/Object.swift

270 lines
7.7 KiB
Swift

//
// Object.swift
// ObjectUI
//
// Created by Leif on 5/24/21.
//
import SwiftFu
import SwiftUI
@dynamicMemberLookup
public class Object: FuableClass, ObservableObject {
public enum ObjectVariable: String, Hashable {
case value
case child
case array
case json
}
/// Variables of the object
@Published public var variables: [AnyHashable: Any] = [:]
/// @dynamicMemberLookup
public subscript(dynamicMember member: String) -> Object {
variable(named: member)
}
// MARK: public init
public init() { }
public convenience init(_ closure: (Object) -> Void) {
self.init()
closure(self)
}
public init(_ value: Any? = nil, _ closure: ((Object) -> Void)? = nil) {
defer {
if let closure = closure {
configure(closure)
}
}
guard let value = value else {
return
}
let unwrappedValue = unwrap(value)
if let _ = unwrappedValue as? NSNull {
return
}
if let object = unwrappedValue as? Object {
consume(object)
} else if let array = unwrappedValue as? [Any] {
consume(Object(array: array))
} else if let dictionary = unwrappedValue as? [AnyHashable: Any] {
consume(Object(dictionary: dictionary))
} else if let data = unwrappedValue as? Data {
consume(Object(data: data))
} else {
consume(Object().set(value: unwrappedValue))
}
}
// MARK: private init
private init(array: [Any]) {
set(
variable: ObjectVariable.array.rawValue,
value: array.map { Object($0) }
)
}
private init(dictionary: [AnyHashable: Any]) {
variables = dictionary
}
private init(data: Data) {
defer {
set(variable: ObjectVariable.json.rawValue, value: String(data: data, encoding: .utf8))
set(value: data)
}
if let json = try? JSONSerialization.jsonObject(with: data,
options: .allowFragments) as? [Any] {
set(variable: ObjectVariable.array.rawValue, value: json)
return
}
guard let json = try? JSONSerialization.jsonObject(with: data,
options: .allowFragments) as? [AnyHashable: Any] else {
return
}
consume(Object(json))
}
}
// MARK: public variables
public extension Object {
var array: [Object] {
if let array = variables[ObjectVariable.array.rawValue] as? [Data] {
return array.map { Object(data: $0) }
} else if let array = variables[ObjectVariable.array.rawValue] as? [Any] {
return array.map { value in
guard let json = value as? [AnyHashable: Any] else {
return Object(value)
}
return Object(dictionary: json)
}
}
return []
}
var child: Object {
(variables[ObjectVariable.child.rawValue] as? Object) ?? Object()
}
var json: Object {
(variables[ObjectVariable.json.rawValue] as? Object) ?? Object()
}
var value: Any {
variables[ObjectVariable.value.rawValue] ?? Object()
}
}
// MARK: public functions
public extension Object {
/// Retrieve a Value from the current object
@discardableResult
func variable(named: AnyHashable) -> Object {
guard let value = variables[named] else {
return Object()
}
if let array = value as? [Any] {
return Object(array: array)
}
guard let object = value as? Object else {
return Object(unwrap(value))
}
return object
}
/// Set a named Value to the current object
@discardableResult
func set(variable named: AnyHashable = ObjectVariable.value.rawValue, value: Any?) -> Self {
guard let value = value,
(unwrap(value) as? NSNull) == nil else {
return self
}
variables[named] = value
return self
}
/// Modify a Value with a name to the current object
@discardableResult
func modify<T>(variable named: AnyHashable = ObjectVariable.value.rawValue, modifier: (T?) -> T?) -> Self {
guard let variable = variables[named],
let value = variable as? T else {
variables[named] = modifier(nil)
return self
}
variables[named] = modifier(value)
return self
}
/// Update a Value with a name to the current object
@discardableResult
func update<T>(variable named: AnyHashable = ObjectVariable.value.rawValue, modifier: (T) -> T) -> Self {
guard let variable = variables[named],
let value = variable as? T else {
return self
}
variables[named] = modifier(value)
return self
}
/// Set the ChildObject with a name of `_object` to the current object
@discardableResult
func set(childObject object: Object) -> Self {
variables[ObjectVariable.child.rawValue] = object
return self
}
/// Set the Array with a name of `_array` to the current object
@discardableResult
func set(array: [Any]) -> Self {
variables[ObjectVariable.array.rawValue] = array
return self
}
@discardableResult
func configure(_ closure: (Object) -> Void) -> Object {
closure(self)
return self
}
@discardableResult
func consume(_ object: Object) -> Object {
object.variables.forEach { (key, value) in
self.set(variable: key, value: value)
}
return self
}
func value<T>(as type: T.Type? = nil) -> T? {
value as? T
}
func value<T>(decodedAs type: T.Type) -> T? where T: Decodable {
guard let data = value(as: Data.self) else {
return nil
}
return try? JSONDecoder().decode(T.self, from: data)
}
}
private extension Object {
/// Unwraps the <Optional> Any type
func unwrap(_ value: Any) -> Any {
let mValue = Mirror(reflecting: value)
let isValueOptional = mValue.displayStyle != .optional
let isValueEmpty = mValue.children.isEmpty
if isValueOptional { return value }
if isValueEmpty { return NSNull() }
guard let (_, unwrappedValue) = mValue.children.first else { return NSNull() }
return unwrappedValue
}
}
extension Object: CustomStringConvertible {
public var description: String {
"""
Object {
\(
variables
.map { (key, value) in
guard let object = value as? Object else {
return "|\t* \(key): \(value) (\(type(of: value)))"
}
let values = object.description.split(separator: "\n")
.dropFirst()
if values.dropLast().isEmpty {
return "|\t* \(key): Object { }"
}
return "|\t* \(key): Object {\n\(values.map { "|\t \($0)" }.joined(separator: "\n"))"
}
.joined(separator: "\n")
)
}
"""
}
}
extension Object: Hashable {
public static func == (lhs: Object, rhs: Object) -> Bool {
lhs.description == rhs.description
}
public func hash(into hasher: inout Hasher) {
hasher.combine(description)
}
}