From 16c7b61108beca746aa3f598a8419dcecb39d017 Mon Sep 17 00:00:00 2001 From: panxi Date: Tue, 12 Nov 2019 17:36:00 -0500 Subject: [PATCH] update with new code, move core code to mvmcore --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 68 +++++++++ .../Models/ActionType/ActionMapModel.swift | 19 +++ .../MVMCore/Models/Extensions/Decoder.swift | 55 ++++++++ .../MVMCore/Models/Extensions/Encoder.swift | 37 +++++ MVMCore/MVMCore/Models/JSON/JSONHelper.swift | 18 +++ MVMCore/MVMCore/Models/JSON/JSONValue.swift | 132 ++++++++++++++++++ MVMCore/MVMCore/Models/Model/Model.swift | 16 +++ .../MVMCore/Models/Model/ModelRegistry.swift | 97 +++++++++++++ 8 files changed, 442 insertions(+) create mode 100644 MVMCore/MVMCore/Models/ActionType/ActionMapModel.swift create mode 100644 MVMCore/MVMCore/Models/Extensions/Decoder.swift create mode 100644 MVMCore/MVMCore/Models/Extensions/Encoder.swift create mode 100644 MVMCore/MVMCore/Models/JSON/JSONHelper.swift create mode 100644 MVMCore/MVMCore/Models/JSON/JSONValue.swift create mode 100644 MVMCore/MVMCore/Models/Model/Model.swift create mode 100644 MVMCore/MVMCore/Models/Model/ModelRegistry.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index dff3445..76072b0 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -39,6 +39,13 @@ 8876D5F31FB50AB000EB2E3D /* UIFont+MFSpacing.m in Sources */ = {isa = PBXBuildFile; fileRef = 8876D5E51FB50AB000EB2E3D /* UIFont+MFSpacing.m */; }; 8876D5F41FB50AB000EB2E3D /* UILabel+MFCustom.h in Headers */ = {isa = PBXBuildFile; fileRef = 8876D5E61FB50AB000EB2E3D /* UILabel+MFCustom.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8876D5F51FB50AB000EB2E3D /* UILabel+MFCustom.m in Sources */ = {isa = PBXBuildFile; fileRef = 8876D5E71FB50AB000EB2E3D /* UILabel+MFCustom.m */; }; + 946EE1A3237B59C30036751F /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946EE1A2237B59C30036751F /* Model.swift */; }; + 946EE1A7237B5B1C0036751F /* ModelRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946EE1A6237B5B1C0036751F /* ModelRegistry.swift */; }; + 946EE1AB237B5C940036751F /* Decoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946EE1AA237B5C940036751F /* Decoder.swift */; }; + 946EE1B0237B5EF70036751F /* JSONHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946EE1AF237B5EF70036751F /* JSONHelper.swift */; }; + 946EE1B2237B5F260036751F /* JSONValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946EE1B1237B5F260036751F /* JSONValue.swift */; }; + 946EE1B4237B619D0036751F /* Encoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946EE1B3237B619D0036751F /* Encoder.swift */; }; + 946EE1BC237B691A0036751F /* ActionMapModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946EE1BB237B691A0036751F /* ActionMapModel.swift */; }; A332F33F20C7231700DCD9D9 /* MVMCoreViewControllerAnimatedTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = A332F33E20C7231600DCD9D9 /* MVMCoreViewControllerAnimatedTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF1201832108C9B400E2F592 /* MVMCoreViewManagerViewControllerProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AF1201812108C9B400E2F592 /* MVMCoreViewManagerViewControllerProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF26DDAE1FCE6A37004E8F65 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = AF26DDB01FCE6A37004E8F65 /* Localizable.strings */; }; @@ -168,6 +175,13 @@ 8876D5E51FB50AB000EB2E3D /* UIFont+MFSpacing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+MFSpacing.m"; sourceTree = ""; }; 8876D5E61FB50AB000EB2E3D /* UILabel+MFCustom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UILabel+MFCustom.h"; sourceTree = ""; }; 8876D5E71FB50AB000EB2E3D /* UILabel+MFCustom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UILabel+MFCustom.m"; sourceTree = ""; }; + 946EE1A2237B59C30036751F /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; + 946EE1A6237B5B1C0036751F /* ModelRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelRegistry.swift; sourceTree = ""; }; + 946EE1AA237B5C940036751F /* Decoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Decoder.swift; sourceTree = ""; }; + 946EE1AF237B5EF70036751F /* JSONHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONHelper.swift; sourceTree = ""; }; + 946EE1B1237B5F260036751F /* JSONValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONValue.swift; sourceTree = ""; }; + 946EE1B3237B619D0036751F /* Encoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encoder.swift; sourceTree = ""; }; + 946EE1BB237B691A0036751F /* ActionMapModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionMapModel.swift; sourceTree = ""; }; A332F33E20C7231600DCD9D9 /* MVMCoreViewControllerAnimatedTransitioning.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewControllerAnimatedTransitioning.h; sourceTree = ""; }; AF1201812108C9B400E2F592 /* MVMCoreViewManagerViewControllerProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewManagerViewControllerProtocol.h; sourceTree = ""; }; AF26DDAF1FCE6A37004E8F65 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -310,6 +324,7 @@ 8876D5CB1FB50A9E00EB2E3D /* MVMCore */ = { isa = PBXGroup; children = ( + 946EE19B237B4DB80036751F /* Models */, AFBB96311FBA341E0008D868 /* Constants */, 8876D5D41FB50AAB00EB2E3D /* Utility */, AF43A7191FC5BE9E008E9347 /* MainProtocols */, @@ -363,6 +378,52 @@ path = Categories; sourceTree = ""; }; + 946EE19B237B4DB80036751F /* Models */ = { + isa = PBXGroup; + children = ( + 946EE1B6237B66630036751F /* ActionType */, + 946EE1A9237B5C720036751F /* Extensions */, + 946EE1A8237B5C650036751F /* Model */, + 946EE1AC237B5CB10036751F /* JSON */, + ); + path = Models; + sourceTree = ""; + }; + 946EE1A8237B5C650036751F /* Model */ = { + isa = PBXGroup; + children = ( + 946EE1A2237B59C30036751F /* Model.swift */, + 946EE1A6237B5B1C0036751F /* ModelRegistry.swift */, + ); + path = Model; + sourceTree = ""; + }; + 946EE1A9237B5C720036751F /* Extensions */ = { + isa = PBXGroup; + children = ( + 946EE1AA237B5C940036751F /* Decoder.swift */, + 946EE1B3237B619D0036751F /* Encoder.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 946EE1AC237B5CB10036751F /* JSON */ = { + isa = PBXGroup; + children = ( + 946EE1AF237B5EF70036751F /* JSONHelper.swift */, + 946EE1B1237B5F260036751F /* JSONValue.swift */, + ); + path = JSON; + sourceTree = ""; + }; + 946EE1B6237B66630036751F /* ActionType */ = { + isa = PBXGroup; + children = ( + 946EE1BB237B691A0036751F /* ActionMapModel.swift */, + ); + path = ActionType; + sourceTree = ""; + }; AF26DDAB1FCE5CF2004E8F65 /* Strings */ = { isa = PBXGroup; children = ( @@ -767,6 +828,7 @@ files = ( AFED77A71FCCA29400BAE689 /* MVMCoreViewControllerProgrammaticMappingObject.m in Sources */, AFBB96A01FBA3A9A0008D868 /* MVMCoreAlertOperation.m in Sources */, + 946EE1A7237B5B1C0036751F /* ModelRegistry.swift in Sources */, AFBB96641FBA3A570008D868 /* MVMCoreLoadHandler.m in Sources */, AFFCFA671FCCC0D700FD0730 /* MVMCoreLoadingOverlayHandler.m in Sources */, AFBB968E1FBA3A9A0008D868 /* MVMCoreNavigationHandler.m in Sources */, @@ -782,10 +844,14 @@ D282AAB62240085300C46919 /* MVMCoreGetterUtility+Extension.swift in Sources */, AFBB965F1FBA3A570008D868 /* MFFreebeeOperation.m in Sources */, AFBB96901FBA3A9A0008D868 /* MVMCoreNavigationObject.m in Sources */, + 946EE1AB237B5C940036751F /* Decoder.swift in Sources */, + 946EE1BC237B691A0036751F /* ActionMapModel.swift in Sources */, AFBB96A61FBA3A9A0008D868 /* MVMCoreTopAlertOperation.m in Sources */, 881D26931FCC9D180079C521 /* MVMCoreErrorObject.m in Sources */, + 946EE1B0237B5EF70036751F /* JSONHelper.swift in Sources */, 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */, 8876D5E91FB50AB000EB2E3D /* NSArray+MFConvenience.m in Sources */, + 946EE1B2237B5F260036751F /* JSONValue.swift in Sources */, AFBB96971FBA3A9A0008D868 /* MVMCorePresentViewControllerOperation.m in Sources */, AFBB96581FBA3A570008D868 /* FreeBeeAuthObject.m in Sources */, AF43A5781FBA5B7C008E9347 /* MVMCoreJSONConstants.m in Sources */, @@ -806,12 +872,14 @@ AF43A7071FC4D7A2008E9347 /* MVMCoreObject.m in Sources */, 01DF561421F90ADC00CC099B /* Dictionary+MFConvenience.swift in Sources */, AFBB96B11FBA3B590008D868 /* MVMCoreDispatchUtility.m in Sources */, + 946EE1A3237B59C30036751F /* Model.swift in Sources */, AFBB965B1FBA3A570008D868 /* FreeBeeUrlObject.m in Sources */, AFEA17A9209B6A1C00BC6740 /* MVMCoreBlockOperation.m in Sources */, AF43A70A1FC4F415008E9347 /* MVMCoreCache.m in Sources */, AF43A6FF1FBE3252008E9347 /* Reachability.m in Sources */, AFBB96921FBA3A9A0008D868 /* MVMCoreNavigationOperation.m in Sources */, AFBB96611FBA3A570008D868 /* MVMCoreLoadObject.m in Sources */, + 946EE1B4237B619D0036751F /* Encoder.swift in Sources */, AFBB96941FBA3A9A0008D868 /* MVMCorePresentAnimationOperation.m in Sources */, AF43A5841FBB66DE008E9347 /* MVMCoreConstants.m in Sources */, AFBB966A1FBA3A570008D868 /* MVMCoreLoadRequestOperation.m in Sources */, diff --git a/MVMCore/MVMCore/Models/ActionType/ActionMapModel.swift b/MVMCore/MVMCore/Models/ActionType/ActionMapModel.swift new file mode 100644 index 0000000..055598c --- /dev/null +++ b/MVMCore/MVMCore/Models/ActionType/ActionMapModel.swift @@ -0,0 +1,19 @@ +// +// ActionMapModel.swift +// MVMCore +// +// Created by Suresh, Kamlesh on 10/3/19. +// Copyright © 2019 Suresh, Kamlesh. All rights reserved. +// + +import Foundation + +@objcMembers public class ActionMapModel: Codable { + public var actionType: String? + public var pageType: String? + + public init(actionType: String?, pageType: String?) { + self.actionType = actionType + self.pageType = pageType + } +} diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder.swift b/MVMCore/MVMCore/Models/Extensions/Decoder.swift new file mode 100644 index 0000000..69a0aa8 --- /dev/null +++ b/MVMCore/MVMCore/Models/Extensions/Decoder.swift @@ -0,0 +1,55 @@ +// +// Decoder.swift +// MVMCore +// +// Created by Matt Brunce on 11/12/19. +// Copyright © 2019 myverizon. All rights reserved. +// + +import Foundation + +public protocol AnyDecoder { + func decode(_ type: T.Type, from data: Data) throws -> T +} + +extension JSONDecoder: AnyDecoder {} +extension PropertyListDecoder: AnyDecoder {} + +extension Data { + public func decode(using decoder: AnyDecoder = JSONDecoder()) throws -> T { + return try decoder.decode(T.self, from: self) + } +} + +extension KeyedDecodingContainerProtocol { + public func decode(forKey key: Key) throws -> T { + return try decode(T.self, forKey: key) + } + + public func decode( + forKey key: Key, + default defaultExpression: @autoclosure () -> T + ) throws -> T { + return try decodeIfPresent(T.self, forKey: key) ?? defaultExpression() + } +} + +extension Decodable { + public static func decode(fileName: String, bundle: Bundle = Bundle.main) throws -> Self { + guard let path = bundle.path(forResource: fileName, ofType: ".json") else { + throw JSONError.pathNotFound + } + + guard let jsonData = try? Data(contentsOf: URL(fileURLWithPath: path)) else { + throw JSONError.data(path: path) + } + + do { + return try jsonData.decode() + } catch { + throw JSONError.other(error: error) + } + } +} + + diff --git a/MVMCore/MVMCore/Models/Extensions/Encoder.swift b/MVMCore/MVMCore/Models/Extensions/Encoder.swift new file mode 100644 index 0000000..baee8ec --- /dev/null +++ b/MVMCore/MVMCore/Models/Extensions/Encoder.swift @@ -0,0 +1,37 @@ +// +// Encoder.swift +// MVMCore +// +// Created by Matt Brunce on 11/12/19. +// Copyright © 2019 myverizon. All rights reserved. +// + +import Foundation + +public protocol AnyEncoder { + func encode(_ value: T) throws -> Data +} + +extension JSONEncoder: AnyEncoder {} +extension PropertyListEncoder: AnyEncoder {} + +extension Encodable { + public func encode(using encoder: AnyEncoder = JSONEncoder()) throws -> Data { + return try encoder.encode(self) + } + + public func toJSON() -> JSONDictionary? { + guard let data = try? self.encode() else { return nil } + return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? JSONDictionary } + } + + public func toJSONString() -> String? { + guard let json = self.toJSON(), + let data = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted), + let string = String(data: data, encoding: .utf8) else{ + return nil + } + return string + } +} + diff --git a/MVMCore/MVMCore/Models/JSON/JSONHelper.swift b/MVMCore/MVMCore/Models/JSON/JSONHelper.swift new file mode 100644 index 0000000..eebb6ab --- /dev/null +++ b/MVMCore/MVMCore/Models/JSON/JSONHelper.swift @@ -0,0 +1,18 @@ +// +// JSONHelper.swift +// MVMCore +// +// Created by Matt Brunce on 11/12/19. +// Copyright © 2019 myverizon. All rights reserved. +// + +import Foundation + +public typealias JSONArray = [[String: Any]] +public typealias JSONDictionary = [String: Any] + +public enum JSONError: Error { + case pathNotFound + case data(path: String) + case other(error: Error) +} diff --git a/MVMCore/MVMCore/Models/JSON/JSONValue.swift b/MVMCore/MVMCore/Models/JSON/JSONValue.swift new file mode 100644 index 0000000..fcb50fd --- /dev/null +++ b/MVMCore/MVMCore/Models/JSON/JSONValue.swift @@ -0,0 +1,132 @@ +// +// JSONValue.swift +// MVMCore +// +// Created by Matt Brunce on 11/12/19. +// Copyright © 2019 myverizon. All rights reserved. +// + +import Foundation + +public typealias JSONValueArray = [[String: JSONValue]] +public typealias JSONValueDictionary = [String: JSONValue] + +public enum JSONValueError: Error { + case encode +} + +extension Optional { + func or(_ other: Optional) -> Optional { + switch self { + case .none: return other + case .some: return self + } + } + + func resolve(with error: @autoclosure () -> Error) throws -> Wrapped { + switch self { + case .none: throw error() + case .some(let wrapped): return wrapped + } + } +} + +public enum JSONValue: Codable, Equatable { + case string(String) + case int(Int) + case double(Double) + case bool(Bool) + case object([String: JSONValue]) + case array([JSONValue]) + case null + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let value = ((try? container.decode(String.self)).map(JSONValue.string)) + .or((try? container.decode(Int.self)).map(JSONValue.int)) + .or((try? container.decode(Double.self)).map(JSONValue.double)) + .or((try? container.decode(Bool.self)).map(JSONValue.bool)) + .or((try? container.decode([String: JSONValue].self)).map(JSONValue.object)) + .or((try? container.decode([JSONValue].self)).map(JSONValue.array)) + + self = value ?? JSONValue.null + } + + func decode() throws -> T { + let encoded = try JSONEncoder().encode(self) + return try JSONDecoder().decode(T.self, from: encoded) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let string): try container.encode(string) + case .int(let int): try container.encode(int) + case .double(let double): try container.encode(double) + case .bool(let bool): try container.encode(bool) + case .object(let object): try container.encode(object) + case .array(let array): try container.encode(array) + case .null: break + } + } +} + +public func ==(lhs: JSONValue, rhs: JSONValue) -> Bool { + let ld = try? lhs.encode() + let rd = try? lhs.encode() + return ld == rd +} + +extension JSONValue: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self = .string(value) + } +} + +extension JSONValue: ExpressibleByIntegerLiteral { + public init(integerLiteral value: Int) { + self = .int(value) + } +} + +extension JSONValue: ExpressibleByFloatLiteral { + public init(floatLiteral value: Double) { + self = .double(value) + } +} + +extension JSONValue: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = .bool(value) + } +} + +extension JSONValue: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: JSONValue...) { + self = .array(elements) + } +} + +extension JSONValue: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, JSONValue)...) { + self = .object([String: JSONValue](uniqueKeysWithValues: elements)) + } +} + +extension Dictionary where Key == String, Value == Any { + public func toJSONValue() throws -> [String: JSONValue] { + let data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) + return try JSONDecoder().decode([String:JSONValue].self, from: data) + } +} + +extension Dictionary where Key == String, Value == JSONValue { + public func toJSONObject() throws -> JSONDictionary { + let encoded = try JSONEncoder().encode(self) + guard let json = try JSONSerialization.jsonObject(with: encoded, options: .mutableContainers) as? JSONDictionary else { + throw JSONValueError.encode + } + return json + } +} + + diff --git a/MVMCore/MVMCore/Models/Model/Model.swift b/MVMCore/MVMCore/Models/Model/Model.swift new file mode 100644 index 0000000..e2adaae --- /dev/null +++ b/MVMCore/MVMCore/Models/Model/Model.swift @@ -0,0 +1,16 @@ +// +// Model.swift +// MVMCore +// +// Created by Matt Brunce on 11/12/19. +// Copyright © 2019 myverizon. All rights reserved. +// + +import Foundation + +public protocol Model: Codable { + + static var identifier: String { get set } + +} + diff --git a/MVMCore/MVMCore/Models/Model/ModelRegistry.swift b/MVMCore/MVMCore/Models/Model/ModelRegistry.swift new file mode 100644 index 0000000..76fc9e4 --- /dev/null +++ b/MVMCore/MVMCore/Models/Model/ModelRegistry.swift @@ -0,0 +1,97 @@ +// +// ModelRegistry.swift +// MVMCore +// +// Created by Matt Brunce on 11/12/19. +// Copyright © 2019 myverizon. All rights reserved. +// + +import Foundation + +public struct ModelRegistry { + public enum Error: Swift.Error { + case keyNotFound + case encoderError + case decoderError + case decoderErrorObjectNotPresent + case decoderErrorModelNotMapped + } + + fileprivate static var types = [String: Model.Type]() + + public static func register(_ type: M.Type) { + types[M.identifier] = type + } +} + + +extension KeyedDecodingContainer where Key : CodingKey{ + public enum TypeCodingKey: String, CodingKey { + //need to discuss this the enum + case moleculeName + case actionMap + } + + //MARK: - Decode + public func decode(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeCodingKey) throws -> T { + guard let model: Model = try decodeIfPresent(codingKey: codingKey, typeCodingKey: typeCodingKey) else { + throw ModelRegistry.Error.decoderErrorObjectNotPresent + } + + guard let m = model as? T else { + throw ModelRegistry.Error.decoderError + } + + return m + } + + public func decode(codingKey: KeyedDecodingContainer.Key) throws -> ActionMapModel { + return try decode(codingKey: codingKey, typeCodingKey: .actionMap) + } + + //MARK: - DecodeIfPresent + public func decodeIfPresent(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeCodingKey) throws -> T? { + + //get the type string + let meta = try self.nestedContainer(keyedBy: TypeCodingKey.self, forKey: codingKey) + guard let type = try meta.decodeIfPresent(String.self, forKey: typeCodingKey) else { + return nil + } + + //get the type + guard let found = ModelRegistry.types[type] else { + throw ModelRegistry.Error.decoderErrorModelNotMapped + } + + //get the decoder for the propertyKey + let decoder = try self.superDecoder(forKey: codingKey) + + //decode the type using the decoder + let model = try found.init(from: decoder) + + guard let m = model as? T else { + throw ModelRegistry.Error.decoderError + } + return m + } + + public func decodeIfPresent(codingKey: KeyedDecodingContainer.Key) throws -> ActionMapModel? { + return try decodeIfPresent(codingKey: codingKey, typeCodingKey: .actionMap) + } +} + + +extension KeyedEncodingContainer where Key : CodingKey { + + public mutating func encodeIfPresent(_ value: Model?, forKey key: KeyedEncodingContainer.Key) throws { + if let v = value { + let encoder = self.superEncoder(forKey: key) + try v.encode(to: encoder) + } + } + + public mutating func encode(_ value: Model, forKey key: KeyedEncodingContainer.Key) throws { + let encoder = self.superEncoder(forKey: key) + try value.encode(to: encoder) + } +}