From cb053464b51638809ce878748c8c571221360416 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 10 May 2022 12:05:57 -0500 Subject: [PATCH 1/6] added userInfo into the Decoders Signed-off-by: Matt Bruce --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 4 + .../Models/Extensions/Decoder+UserInfo.swift | 109 ++++++++++++++++++ .../MVMCore/Models/Extensions/Decoder.swift | 45 ++------ 3 files changed, 125 insertions(+), 33 deletions(-) create mode 100644 MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index edfc67b..f396c8e 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -158,6 +158,7 @@ D2DEDCB923C6400600C44CC4 /* UnitInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DEDCB823C6400600C44CC4 /* UnitInterval.swift */; }; D2DEDCBB23C65BC300C44CC4 /* Percent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DEDCBA23C65BC300C44CC4 /* Percent.swift */; }; D2E1FAD92260C3E400AEFD8C /* DelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FAD82260C3E400AEFD8C /* DelegateObject.swift */; }; + EA09CD62282ACDDB00A7835F /* Decoder+UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CD61282ACDDB00A7835F /* Decoder+UserInfo.swift */; }; EA3B264C25FC0B7600008074 /* ModelHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3B264B25FC0B7600008074 /* ModelHandlerProtocol.swift */; }; /* End PBXBuildFile section */ @@ -307,6 +308,7 @@ D2DEDCB823C6400600C44CC4 /* UnitInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitInterval.swift; sourceTree = ""; }; D2DEDCBA23C65BC300C44CC4 /* Percent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Percent.swift; sourceTree = ""; }; D2E1FAD82260C3E400AEFD8C /* DelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateObject.swift; sourceTree = ""; }; + EA09CD61282ACDDB00A7835F /* Decoder+UserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decoder+UserInfo.swift"; sourceTree = ""; }; EA3B264B25FC0B7600008074 /* ModelHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelHandlerProtocol.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -435,6 +437,7 @@ isa = PBXGroup; children = ( 946EE1AA237B5C940036751F /* Decoder.swift */, + EA09CD61282ACDDB00A7835F /* Decoder+UserInfo.swift */, 946EE1B3237B619D0036751F /* Encoder.swift */, 0A42538E23F3414800554656 /* Codable+Helpers.swift */, ); @@ -911,6 +914,7 @@ AFEA17A9209B6A1C00BC6740 /* MVMCoreBlockOperation.m in Sources */, AF43A70A1FC4F415008E9347 /* MVMCoreCache.m in Sources */, AF43A6FF1FBE3252008E9347 /* Reachability.m in Sources */, + EA09CD62282ACDDB00A7835F /* Decoder+UserInfo.swift in Sources */, 01C851D123CF97FE0021F976 /* ActionBackModel.swift in Sources */, D27073D125BB844B001C7246 /* MVMCoreActionDelegateProtocol+Extension.swift in Sources */, AFBB96921FBA3A9A0008D868 /* MVMCoreNavigationOperation.m in Sources */, diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift b/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift new file mode 100644 index 0000000..d9f4e04 --- /dev/null +++ b/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift @@ -0,0 +1,109 @@ +// +// Decoder+UserInfo.swift +// MVMCore +// +// Created by Matt Bruce on 5/10/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +extension CodingUserInfoKey { + internal static let delegateObjectKey = CodingUserInfoKey(rawValue: "delegateObject")! + internal static let contextKey = CodingUserInfoKey(rawValue: "context")! +} + +// MARK: - DelegateObject +public extension JSONDecoder { + /// Adds a delegate object to the decoder for use in the model initializers. + func add(value: Any, for key: CodingUserInfoKey) { + userInfo.updateValue(value, forKey: key) + } +} + +public extension Decoder { + /// Gets a delegate object from the decoder for use in the model initializers. Had to be added before decode. + func get() throws -> T? { + return userInfo[.delegateObjectKey] as? T + } +} + +// MARK: - DecodingContext +/// Class used to be added into the Decoder's userInfo object to store +/// key/value pairs to be used within the Decoding process. +public class DecodingContext { + + /// Key/Value store + fileprivate var userInfo: [String: Any] = [:] + + public init(){ } + + /// Used to access value + /// - Parameter key: key for value + /// - Returns: value returns if the key exists + public func value(forKey key: String) -> Any? { + return userInfo[key] + } +} + +extension Decoder { + + ///wrapping getter to call internal userInfo for this object + public var context: DecodingContext? { + userInfo[.contextKey] as? DecodingContext + } + + /// Gets Typed value for key in the context + /// - Parameter key: Identifier + /// - Returns: Typed value + public func getContext(key: String) throws -> T? { + context?.userInfo[key] as? T + } + + /// Sets value for key in the context + /// - Parameters: + /// - value: Object + /// - key: Identifier + public func setContext(value: Any, for key: String) { + context?.userInfo[key] = value + } + + /// Removes value for key in the context + /// - Parameter key: Identifier + public func removeContext(for key: String) { + context?.userInfo.removeValue(forKey: key) + } + + /// This is a wrapping function that is meant to be used along with a "decode" method. It will save a key/value pair locally + /// and then this key/value pair is removed immediately after the completion handler is executed so that no other classes + /// will mistakenly use this value. + /// - Parameters: + /// - value: Object that you are wanting to save + /// - key: Identifier to get the Value + /// - completion: Code that will run after the value is set + public func setContext(value: Any, for key: String, completion: (() throws -> Void)) throws { + setContext(value: value, for: key) + try completion() + removeContext(for: key) + } +} + +extension JSONDecoder { + + /// Helper method to initialize a JSONDecoder + /// - Parameter delegateObject: Delegate Object + /// - Returns: JSONDecoder + public class func create(delegateObject: DelegateObject? = nil) throws -> JSONDecoder { + let decoder = JSONDecoder() + decoder.add(value: DecodingContext(), for: .contextKey) + if let delegateObject = delegateObject { + decoder.add(value: delegateObject, for: .delegateObjectKey) + } + return decoder + } + + ///wrapping getter to call internal userInfo for this object + public var context: DecodingContext? { + get { return userInfo[.contextKey] as? DecodingContext } + } +} diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder.swift b/MVMCore/MVMCore/Models/Extensions/Decoder.swift index fc54a0d..9a4dd94 100644 --- a/MVMCore/MVMCore/Models/Extensions/Decoder.swift +++ b/MVMCore/MVMCore/Models/Extensions/Decoder.swift @@ -25,6 +25,16 @@ extension Data { } } +extension JSONDecoder { + /// Decodes a top-level value of the given type from the given JSON representation, and adds the delegate object if provided. + func decode(_ type: T.Type, from data: Data, delegateObject: DelegateObject?) throws -> T where T : Decodable { + if let delegateObject = delegateObject { + add(value: delegateObject, for: .delegateObjectKey) + } + return try decode(T.self, from: data) + } +} + extension KeyedDecodingContainerProtocol { public func decode(forKey key: Key) throws -> T { return try decode(T.self, forKey: key) @@ -42,7 +52,8 @@ extension Decodable { public static func decode(jsonDict: [String: Any], delegateObject: DelegateObject? = nil) throws -> Self { let jsonData = try JSONSerialization.data(withJSONObject: jsonDict) do { - return try jsonData.decode(delegateObject: delegateObject) + let decoder = try JSONDecoder.create(delegateObject: delegateObject) + return try jsonData.decode(using: decoder) } catch { throw JSONError.other(error: error) } @@ -64,35 +75,3 @@ extension Decodable { } } } - -public enum DecoderKeyError: Error { - case createKey -} - -public extension JSONDecoder { - /// Adds a delegate object to the decoder for use in the model initializers. - func add(delegateObject: T) throws { - guard let key = CodingUserInfoKey(rawValue: "delegateObject") else { - throw DecoderKeyError.createKey - } - userInfo.updateValue(delegateObject, forKey: key) - } - - /// Decodes a top-level value of the given type from the given JSON representation, and adds the delegate object if provided. - func decode(_ type: T.Type, from data: Data, delegateObject: DelegateObject?) throws -> T where T : Decodable { - if let delegateObject = delegateObject { - try add(delegateObject: delegateObject) - } - return try decode(T.self, from: data) - } -} - -public extension Decoder { - /// Gets a delegate object from the decoder for use in the model initializers. Had to be added before decode. - func get() throws -> T? { - guard let key = CodingUserInfoKey(rawValue: "delegateObject") else { - throw DecoderKeyError.createKey - } - return userInfo[key] as? T - } -} From 1718f87f403da923278ab656eab6651ca95e3b81 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 10 May 2022 12:06:18 -0500 Subject: [PATCH 2/6] updated to user the JSONDecoder.create() Signed-off-by: Matt Bruce --- .../ActionType/Client Parameters/ClientParameterHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift index fe420ef..a4329e7 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift @@ -26,7 +26,7 @@ func getClientParameterModel(_ clientParameters: [String: Any]) throws -> ClientParameterModel? { let data = try JSONSerialization.data(withJSONObject: clientParameters) - return try JSONDecoder().decode(ClientParameterModel.self, from: data) + return try JSONDecoder.create().decode(ClientParameterModel.self, from: data) } /// Sample clientParameters From a7e32f3ca4237aad93a0cd61fec796d545aab825 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 10 May 2022 12:16:30 -0500 Subject: [PATCH 3/6] removed try Signed-off-by: Matt Bruce --- MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift | 2 +- MVMCore/MVMCore/Models/Extensions/Decoder.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift b/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift index d9f4e04..d8ffda1 100644 --- a/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift +++ b/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift @@ -93,7 +93,7 @@ extension JSONDecoder { /// Helper method to initialize a JSONDecoder /// - Parameter delegateObject: Delegate Object /// - Returns: JSONDecoder - public class func create(delegateObject: DelegateObject? = nil) throws -> JSONDecoder { + public class func create(delegateObject: DelegateObject? = nil) -> JSONDecoder { let decoder = JSONDecoder() decoder.add(value: DecodingContext(), for: .contextKey) if let delegateObject = delegateObject { diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder.swift b/MVMCore/MVMCore/Models/Extensions/Decoder.swift index 9a4dd94..0c0311b 100644 --- a/MVMCore/MVMCore/Models/Extensions/Decoder.swift +++ b/MVMCore/MVMCore/Models/Extensions/Decoder.swift @@ -16,7 +16,7 @@ extension JSONDecoder: AnyDecoder {} extension PropertyListDecoder: AnyDecoder {} extension Data { - public func decode(using decoder: AnyDecoder = JSONDecoder(), delegateObject: DelegateObject? = nil) throws -> T { + public func decode(using decoder: AnyDecoder = JSONDecoder.create(), delegateObject: DelegateObject? = nil) throws -> T { if let decoder = decoder as? JSONDecoder { return try decoder.decode(T.self, from: self, delegateObject: delegateObject) } else { @@ -52,7 +52,7 @@ extension Decodable { public static func decode(jsonDict: [String: Any], delegateObject: DelegateObject? = nil) throws -> Self { let jsonData = try JSONSerialization.data(withJSONObject: jsonDict) do { - let decoder = try JSONDecoder.create(delegateObject: delegateObject) + let decoder = JSONDecoder.create(delegateObject: delegateObject) return try jsonData.decode(using: decoder) } catch { throw JSONError.other(error: error) From 9bb90b2153bea5edd4a03bbc125a612cd1029f19 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 10 May 2022 14:49:59 -0500 Subject: [PATCH 4/6] refactored create method Signed-off-by: Matt Bruce --- .../Models/Extensions/Decoder+UserInfo.swift | 14 +++++++++----- MVMCore/MVMCore/Models/Extensions/Decoder.swift | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift b/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift index d8ffda1..686b241 100644 --- a/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift +++ b/MVMCore/MVMCore/Models/Extensions/Decoder+UserInfo.swift @@ -9,15 +9,15 @@ import Foundation extension CodingUserInfoKey { - internal static let delegateObjectKey = CodingUserInfoKey(rawValue: "delegateObject")! - internal static let contextKey = CodingUserInfoKey(rawValue: "context")! + public static let delegateObjectKey = CodingUserInfoKey(rawValue: "delegateObject")! + public static let contextKey = CodingUserInfoKey(rawValue: "context")! } // MARK: - DelegateObject public extension JSONDecoder { /// Adds a delegate object to the decoder for use in the model initializers. - func add(value: Any, for key: CodingUserInfoKey) { - userInfo.updateValue(value, forKey: key) + func add(delegateObject: T) { + add(value: delegateObject, for: .delegateObjectKey) } } @@ -89,11 +89,15 @@ extension Decoder { } extension JSONDecoder { + /// Adds a key/value to the decoder for use in the model initializers. + func add(value: Any, for key: CodingUserInfoKey) { + userInfo.updateValue(value, forKey: key) + } /// Helper method to initialize a JSONDecoder /// - Parameter delegateObject: Delegate Object /// - Returns: JSONDecoder - public class func create(delegateObject: DelegateObject? = nil) -> JSONDecoder { + public class func create(with delegateObject: DelegateObject? = nil) -> JSONDecoder { let decoder = JSONDecoder() decoder.add(value: DecodingContext(), for: .contextKey) if let delegateObject = delegateObject { diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder.swift b/MVMCore/MVMCore/Models/Extensions/Decoder.swift index 0c0311b..e4b5863 100644 --- a/MVMCore/MVMCore/Models/Extensions/Decoder.swift +++ b/MVMCore/MVMCore/Models/Extensions/Decoder.swift @@ -52,7 +52,7 @@ extension Decodable { public static func decode(jsonDict: [String: Any], delegateObject: DelegateObject? = nil) throws -> Self { let jsonData = try JSONSerialization.data(withJSONObject: jsonDict) do { - let decoder = JSONDecoder.create(delegateObject: delegateObject) + let decoder = JSONDecoder.create(with: delegateObject) return try jsonData.decode(using: decoder) } catch { throw JSONError.other(error: error) From 9e9421f66a6847a3a3bbc6e53733720b2200b844 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 11 May 2022 09:17:39 -0500 Subject: [PATCH 5/6] added property wrapper for default values Signed-off-by: Matt Bruce --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 4 + .../PropertyWrappers/DecodableDefault.swift | 87 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 MVMCore/MVMCore/Utility/PropertyWrappers/DecodableDefault.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index f396c8e..e311e42 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -159,6 +159,7 @@ D2DEDCBB23C65BC300C44CC4 /* Percent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2DEDCBA23C65BC300C44CC4 /* Percent.swift */; }; D2E1FAD92260C3E400AEFD8C /* DelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FAD82260C3E400AEFD8C /* DelegateObject.swift */; }; EA09CD62282ACDDB00A7835F /* Decoder+UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CD61282ACDDB00A7835F /* Decoder+UserInfo.swift */; }; + EA09CD99282BF83600A7835F /* DecodableDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA09CD98282BF83600A7835F /* DecodableDefault.swift */; }; EA3B264C25FC0B7600008074 /* ModelHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3B264B25FC0B7600008074 /* ModelHandlerProtocol.swift */; }; /* End PBXBuildFile section */ @@ -309,6 +310,7 @@ D2DEDCBA23C65BC300C44CC4 /* Percent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Percent.swift; sourceTree = ""; }; D2E1FAD82260C3E400AEFD8C /* DelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DelegateObject.swift; sourceTree = ""; }; EA09CD61282ACDDB00A7835F /* Decoder+UserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decoder+UserInfo.swift"; sourceTree = ""; }; + EA09CD98282BF83600A7835F /* DecodableDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodableDefault.swift; sourceTree = ""; }; EA3B264B25FC0B7600008074 /* ModelHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelHandlerProtocol.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -688,6 +690,7 @@ D2DEDCB623C63F3B00C44CC4 /* Clamping.swift */, D2DEDCB823C6400600C44CC4 /* UnitInterval.swift */, D2DEDCBA23C65BC300C44CC4 /* Percent.swift */, + EA09CD98282BF83600A7835F /* DecodableDefault.swift */, ); path = PropertyWrappers; sourceTree = ""; @@ -915,6 +918,7 @@ AF43A70A1FC4F415008E9347 /* MVMCoreCache.m in Sources */, AF43A6FF1FBE3252008E9347 /* Reachability.m in Sources */, EA09CD62282ACDDB00A7835F /* Decoder+UserInfo.swift in Sources */, + EA09CD99282BF83600A7835F /* DecodableDefault.swift in Sources */, 01C851D123CF97FE0021F976 /* ActionBackModel.swift in Sources */, D27073D125BB844B001C7246 /* MVMCoreActionDelegateProtocol+Extension.swift in Sources */, AFBB96921FBA3A9A0008D868 /* MVMCoreNavigationOperation.m in Sources */, diff --git a/MVMCore/MVMCore/Utility/PropertyWrappers/DecodableDefault.swift b/MVMCore/MVMCore/Utility/PropertyWrappers/DecodableDefault.swift new file mode 100644 index 0000000..b891942 --- /dev/null +++ b/MVMCore/MVMCore/Utility/PropertyWrappers/DecodableDefault.swift @@ -0,0 +1,87 @@ +// +// DecoderDefault.swift +// MVMCore +// +// Created by Matt Bruce on 5/11/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +/// https://www.swiftbysundell.com/tips/default-decoding-values/ +/// This code is used for Decodable objects that allow Default Values to be added. +public protocol DecodableDefaultSource { + associatedtype Value: Decodable + static var defaultValue: Value { get } +} + +public enum DecodableDefault {} + +extension DecodableDefault { + @propertyWrapper + public struct Wrapper { + public typealias Value = Source.Value + public var wrappedValue = Source.defaultValue + public init() {} + } +} + +extension DecodableDefault.Wrapper: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + wrappedValue = try container.decode(Value.self) + } +} + +extension KeyedDecodingContainer { + public func decode(_ type: DecodableDefault.Wrapper.Type, + forKey key: Key) throws -> DecodableDefault.Wrapper { + try decodeIfPresent(type, forKey: key) ?? .init() + } +} + +extension DecodableDefault { + public typealias Source = DecodableDefaultSource + public typealias List = Decodable & ExpressibleByArrayLiteral + public typealias Map = Decodable & ExpressibleByDictionaryLiteral + + public enum Sources { + public enum True: Source { + public static var defaultValue: Bool { true } + } + + public enum False: Source { + public static var defaultValue: Bool { false } + } + + public enum EmptyString: Source { + public static var defaultValue: String { "" } + } + + public enum EmptyList: Source { + public static var defaultValue: T { [] } + } + + public enum EmptyMap: Source { + public static var defaultValue: T { [:] } + } + } +} + +extension DecodableDefault { + public typealias True = Wrapper + public typealias False = Wrapper + public typealias EmptyString = Wrapper + public typealias EmptyList = Wrapper> + public typealias EmptyMap = Wrapper> +} + +extension DecodableDefault.Wrapper: Equatable where Value: Equatable {} +extension DecodableDefault.Wrapper: Hashable where Value: Hashable {} + +extension DecodableDefault.Wrapper: Encodable where Value: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(wrappedValue) + } +} From 071cb16870dfc96e22c9c8f575ad31cba26ff2e2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 24 May 2022 12:07:03 -0500 Subject: [PATCH 6/6] ensure context exists for JSONDecoder within extension method Signed-off-by: Matt Bruce --- MVMCore/MVMCore/Models/Extensions/Decoder.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MVMCore/MVMCore/Models/Extensions/Decoder.swift b/MVMCore/MVMCore/Models/Extensions/Decoder.swift index e4b5863..ca66508 100644 --- a/MVMCore/MVMCore/Models/Extensions/Decoder.swift +++ b/MVMCore/MVMCore/Models/Extensions/Decoder.swift @@ -28,6 +28,9 @@ extension Data { extension JSONDecoder { /// Decodes a top-level value of the given type from the given JSON representation, and adds the delegate object if provided. func decode(_ type: T.Type, from data: Data, delegateObject: DelegateObject?) throws -> T where T : Decodable { + if context == nil { + add(value: DecodingContext(), for: .contextKey) + } if let delegateObject = delegateObject { add(value: delegateObject, for: .delegateObjectKey) }