From 7ca45c43a39646adacc7a48304a230717af1d798 Mon Sep 17 00:00:00 2001 From: iwill Date: Thu, 1 Jul 2021 11:30:58 +0800 Subject: [PATCH 01/11] sty: comments --- Sources/ExCodable/ExCodable+DEPRECATED.swift | 35 +++++++++++ Sources/ExCodable/ExCodable.swift | 61 +++++++------------- 2 files changed, 56 insertions(+), 40 deletions(-) create mode 100644 Sources/ExCodable/ExCodable+DEPRECATED.swift diff --git a/Sources/ExCodable/ExCodable+DEPRECATED.swift b/Sources/ExCodable/ExCodable+DEPRECATED.swift new file mode 100644 index 0000000..6666bb6 --- /dev/null +++ b/Sources/ExCodable/ExCodable+DEPRECATED.swift @@ -0,0 +1,35 @@ +// +// ExCodable.swift +// ExCodable +// +// Created by Mr. Ming on 2021-07-01. +// Copyright (c) 2021 Mr. Ming . Released under the MIT license. +// + +import Foundation + +public extension ExCodable { + @available(*, deprecated, renamed: "encode(to:with:nonnull:throws:)") + func encode(with keyMapping: [KeyMap], using encoder: Encoder) { + try? encode(to: encoder, with: keyMapping) + } + @available(*, deprecated, renamed: "decode(from:with:nonnull:throws:)") + mutating func decode(with keyMapping: [KeyMap], using decoder: Decoder) { + try? decode(from: decoder, with: keyMapping) + } + @available(*, deprecated, renamed: "decodeReference(from:with:nonnull:throws:)") + func decodeReference(with keyMapping: [KeyMap], using decoder: Decoder) { + try? decodeReference(from: decoder, with: keyMapping) + } +} + +@available(*, deprecated, renamed: "append(decodingTypeConverter:)") +public protocol KeyedDecodingContainerCustomTypeConversion: ExCodableDecodingTypeConverter { + func decodeForTypeConversion(_ container: KeyedDecodingContainer, codingKey: K, as type: T.Type) -> T? +} +@available(*, deprecated) +public extension KeyedDecodingContainerCustomTypeConversion { + func decode(_ container: KeyedDecodingContainer, codingKey: K, as type: T.Type) throws -> T? { + return decodeForTypeConversion(container, codingKey: codingKey, as: type) + } +} diff --git a/Sources/ExCodable/ExCodable.swift b/Sources/ExCodable/ExCodable.swift index 9e873eb..06bdb23 100644 --- a/Sources/ExCodable/ExCodable.swift +++ b/Sources/ExCodable/ExCodable.swift @@ -24,7 +24,18 @@ public protocol ExCodable: Codable { static var keyMapping: [KeyMap] { get } } +// default implementation for Encodable +public extension ExCodable where Root == Self { + func encode(to encoder: Encoder) throws { + try encode(to: encoder, with: Self.keyMapping) + } +} + +// MARK: - keyMapping + +// encode/decode public extension ExCodable { + static var keyMapping: [KeyMap] { [] } // default implementation for optional property func encode(to encoder: Encoder, with keyMapping: [KeyMap], nonnull: Bool = false, throws: Bool = false) throws { try keyMapping.forEach { try $0.encode(self, encoder, nonnull, `throws`) } } @@ -35,13 +46,6 @@ public extension ExCodable { try keyMapping.forEach { try $0.decodeReference?(self, decoder, nonnull, `throws`) } } } -public extension ExCodable where Root == Self { - func encode(to encoder: Encoder) throws { - try encode(to: encoder, with: Self.keyMapping) - } -} - -// MARK: - public final class KeyMap { fileprivate let encode: (_ root: Root, _ encoder: Encoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void @@ -119,7 +123,7 @@ public extension Decoder { // , abortIfNull nonnull: Bool = false, abortOnError } } -// MARK: - +// MARK: - Encoder&Decoder public extension Encoder { @@ -225,13 +229,18 @@ public extension Decoder { } } -private struct ExCodingKey: CodingKey { - let stringValue: String, intValue: Int? +// MARK: - ExCodingKey + +private struct ExCodingKey { + public let stringValue: String, intValue: Int? init(_ stringValue: String) { (self.stringValue, self.intValue) = (stringValue, nil) } init(_ stringValue: Substring) { self.init(String(stringValue)) } - init?(stringValue: String) { self.init(stringValue) } init(_ intValue: Int) { (self.intValue, self.stringValue) = (intValue, String(intValue)) } - init?(intValue: Int) { self.init(intValue) } +} + +extension ExCodingKey: CodingKey { + public init?(stringValue: String) { self.init(stringValue) } + public init?(intValue: Int) { self.init(intValue) } } // MARK: - alternative-keys + nested-keys + type-conversion @@ -498,31 +507,3 @@ extension JSONDecoder: DataDecoder {} extension PropertyListEncoder: DataEncoder {} extension PropertyListDecoder: DataDecoder {} #endif - -// MARK: - #### DEPRECATED #### - -public extension ExCodable { - @available(*, deprecated, renamed: "encode(to:with:nonnull:throws:)") - func encode(with keyMapping: [KeyMap], using encoder: Encoder) { - try? encode(to: encoder, with: keyMapping) - } - @available(*, deprecated, renamed: "decode(from:with:nonnull:throws:)") - mutating func decode(with keyMapping: [KeyMap], using decoder: Decoder) { - try? decode(from: decoder, with: keyMapping) - } - @available(*, deprecated, renamed: "decodeReference(from:with:nonnull:throws:)") - func decodeReference(with keyMapping: [KeyMap], using decoder: Decoder) { - try? decodeReference(from: decoder, with: keyMapping) - } -} - -@available(*, deprecated, renamed: "append(decodingTypeConverter:)") -public protocol KeyedDecodingContainerCustomTypeConversion: ExCodableDecodingTypeConverter { - func decodeForTypeConversion(_ container: KeyedDecodingContainer, codingKey: K, as type: T.Type) -> T? -} -@available(*, deprecated) -public extension KeyedDecodingContainerCustomTypeConversion { - func decode(_ container: KeyedDecodingContainer, codingKey: K, as type: T.Type) throws -> T? { - return decodeForTypeConversion(container, codingKey: codingKey, as: type) - } -} From 686f7120406b91ab9fa22c11b8e4d51d74340a3b Mon Sep 17 00:00:00 2001 From: iwill Date: Fri, 2 Jul 2021 00:01:15 +0800 Subject: [PATCH 02/11] !!!: 1.x, keyMapping -> propertyWrapper --- ExCodable.podspec | 2 +- Sources/ExCodable/ExCodable+DEPRECATED.swift | 95 ++++++++++- Sources/ExCodable/ExCodable.swift | 167 ++++++++++--------- Tests/ExCodableTests/ExCodableTests.swift | 165 +++++------------- 4 files changed, 228 insertions(+), 201 deletions(-) diff --git a/ExCodable.podspec b/ExCodable.podspec index 070e872..fa839ab 100644 --- a/ExCodable.podspec +++ b/ExCodable.podspec @@ -3,7 +3,7 @@ Pod::Spec.new do |s| # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.name = "ExCodable" # export LIB_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) - s.version = ENV["LIB_VERSION"] || "0.5.0" + s.version = ENV["LIB_VERSION"] || "1.0.0-alpha" s.summary = "Key-Mapping Extensions for Swift Codable" # s.description = "Key-Mapping Extensions for Swift Codable." s.homepage = "https://github.com/iwill/ExCodable" diff --git a/Sources/ExCodable/ExCodable+DEPRECATED.swift b/Sources/ExCodable/ExCodable+DEPRECATED.swift index 6666bb6..7046def 100644 --- a/Sources/ExCodable/ExCodable+DEPRECATED.swift +++ b/Sources/ExCodable/ExCodable+DEPRECATED.swift @@ -8,7 +8,100 @@ import Foundation -public extension ExCodable { +// MARK: - keyMapping + +@available(*, deprecated, message: "use `@ExCodable` property wrapper instead") +public protocol ExCodableProtocol: Codable { + associatedtype Root = Self where Root: ExCodableProtocol + static var keyMapping: [KeyMap] { get } +} + +@available(*, deprecated) +public extension ExCodableProtocol where Root == Self { + + // default implementation of ExCodableProtocol + static var keyMapping: [KeyMap] { [] } + + // default implementation of Encodable + func encode(to encoder: Encoder) throws { + try encode(to: encoder, with: Self.keyMapping) + try encode(to: encoder, nonnull: false, throws: false) + } + + func decode(from decoder: Decoder) throws { + try decode(from: decoder, nonnull: false, throws: false) + } +} + +@available(*, deprecated) +public extension ExCodableProtocol { + func encode(to encoder: Encoder, with keyMapping: [KeyMap], nonnull: Bool = false, throws: Bool = false) throws { + try keyMapping.forEach { try $0.encode(self, encoder, nonnull, `throws`) } + } + mutating func decode(from decoder: Decoder, with keyMapping: [KeyMap], nonnull: Bool = false, throws: Bool = false) throws { + try keyMapping.forEach { try $0.decode?(&self, decoder, nonnull, `throws`) } + } + func decodeReference(from decoder: Decoder, with keyMapping: [KeyMap], nonnull: Bool = false, throws: Bool = false) throws { + try keyMapping.forEach { try $0.decodeReference?(self, decoder, nonnull, `throws`) } + } +} + +@available(*, deprecated, message: "use `@ExCodable` property wrapper instead") +public final class KeyMap { + fileprivate let encode: (_ root: Root, _ encoder: Encoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void + fileprivate let decode: ((_ root: inout Root, _ decoder: Decoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void)? + fileprivate let decodeReference: ((_ root: Root, _ decoder: Decoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void)? + private init(encode: @escaping (_ root: Root, _ encoder: Encoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void, + decode: ((_ root: inout Root, _ decoder: Decoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void)?, + decodeReference: ((_ root: Root, _ decoder: Decoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void)?) { + (self.encode, self.decode, self.decodeReference) = (encode, decode, decodeReference) + } +} + +@available(*, deprecated) +public extension KeyMap { + convenience init(_ keyPath: WritableKeyPath, to codingKeys: String ..., nonnull: Bool? = nil, throws: Bool? = nil) { + self.init(encode: { root, encoder, nonnullAll, throwsAll in + try encoder.encode(root[keyPath: keyPath], for: codingKeys.first!, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) + }, decode: { root, decoder, nonnullAll, throwsAll in + if let value: Value = try decoder.decode(codingKeys, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) { + root[keyPath: keyPath] = value + } + }, decodeReference: nil) + } + convenience init(_ keyPath: WritableKeyPath, to codingKeys: Key ..., nonnull: Bool? = nil, throws: Bool? = nil) { + self.init(encode: { root, encoder, nonnullAll, throwsAll in + try encoder.encode(root[keyPath: keyPath], for: codingKeys.first!, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) + }, decode: { root, decoder, nonnullAll, throwsAll in + if let value: Value = try decoder.decode(codingKeys, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) { + root[keyPath: keyPath] = value + } + }, decodeReference: nil) + } + convenience init(ref keyPath: ReferenceWritableKeyPath, to codingKeys: String ..., nonnull: Bool? = nil, throws: Bool? = nil) { + self.init(encode: { root, encoder, nonnullAll, throwsAll in + try encoder.encode(root[keyPath: keyPath], for: codingKeys.first!, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) + }, decode: nil, decodeReference: { root, decoder, nonnullAll, throwsAll in + if let value: Value = try decoder.decode(codingKeys, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) { + root[keyPath: keyPath] = value + } + }) + } + convenience init(ref keyPath: ReferenceWritableKeyPath, to codingKeys: Key ..., nonnull: Bool? = nil, throws: Bool? = nil) { + self.init(encode: { root, encoder, nonnullAll, throwsAll in + try encoder.encode(root[keyPath: keyPath], for: codingKeys.first!, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) + }, decode: nil, decodeReference: { root, decoder, nonnullAll, throwsAll in + if let value: Value = try decoder.decode(codingKeys, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) { + root[keyPath: keyPath] = value + } + }) + } +} + +// MARK: - + +@available(*, deprecated) +public extension ExCodableProtocol { @available(*, deprecated, renamed: "encode(to:with:nonnull:throws:)") func encode(with keyMapping: [KeyMap], using encoder: Encoder) { try? encode(to: encoder, with: keyMapping) diff --git a/Sources/ExCodable/ExCodable.swift b/Sources/ExCodable/ExCodable.swift index 06bdb23..c66f47c 100644 --- a/Sources/ExCodable/ExCodable.swift +++ b/Sources/ExCodable/ExCodable.swift @@ -18,86 +18,104 @@ import Foundation * * - seealso: [Usage](https://github.com/iwill/ExCodable#usage) from GitGub * - seealso: `ExCodableTests.swift` form the source code + * - seealso: Idea from [Decoding and overriding](https://www.swiftbysundell.com/articles/property-wrappers-in-swift/#decoding-and-overriding), by John Sundell. */ -public protocol ExCodable: Codable { - associatedtype Root = Self where Root: ExCodable - static var keyMapping: [KeyMap] { get } -} -// default implementation for Encodable -public extension ExCodable where Root == Self { - func encode(to encoder: Encoder) throws { - try encode(to: encoder, with: Self.keyMapping) +@propertyWrapper +public final class ExCodable { + fileprivate let stringKeys: [String]? + fileprivate let nonnull, `throws`: Bool? + fileprivate let encode: ((_ encoder: Encoder, _ value: Value) throws -> Void)?, decode: ((_ decoder: Decoder) throws -> Value?)? + public var wrappedValue: Value + private init(wrappedValue: Value, stringKeys: [String]? = nil, nonnull: Bool? = nil, throws: Bool? = nil, encode: ((_ encoder: Encoder, _ value: Value) throws -> Void)?, decode: ((_ decoder: Decoder) throws -> Value?)?) { + (self.wrappedValue, self.stringKeys, self.nonnull, self.throws, self.encode, self.decode) = (wrappedValue, stringKeys, nonnull, `throws`, encode, decode) + } + public convenience init(wrappedValue: Value, _ stringKey: String? = nil, nonnull: Bool? = nil, throws: Bool? = nil, encode: ((_ encoder: Encoder, _ value: Value) throws -> Void)? = nil, decode: ((_ decoder: Decoder) throws -> Value?)? = nil) { + self.init(wrappedValue: wrappedValue, stringKeys: stringKey.map { [$0] }, nonnull: nonnull, throws: `throws`, encode: encode, decode: decode) + } + public convenience init(wrappedValue: Value, _ stringKeys: String..., nonnull: Bool? = nil, throws: Bool? = nil, encode: ((_ encoder: Encoder, _ value: Value) throws -> Void)? = nil, decode: ((_ decoder: Decoder) throws -> Value?)? = nil) { + self.init(wrappedValue: wrappedValue, stringKeys: stringKeys, nonnull: nonnull, throws: `throws`, encode: encode, decode: decode) + } + public convenience init(wrappedValue: Value, _ codingKeys: CodingKey..., nonnull: Bool? = nil, throws: Bool? = nil, encode: ((_ encoder: Encoder, _ value: Value) throws -> Void)? = nil, decode: ((_ decoder: Decoder) throws -> Value?)? = nil) { + self.init(wrappedValue: wrappedValue, stringKeys: codingKeys.map { $0.stringValue }, nonnull: nonnull, throws: `throws`, encode: encode, decode: decode) + } +} +extension ExCodable: Equatable where Value: Equatable { + public static func == (lhs: ExCodable, rhs: ExCodable) -> Bool { + return lhs.wrappedValue == rhs.wrappedValue } } -// MARK: - keyMapping +fileprivate protocol EncodablePropertyWrapper { + func encode(to encoder: Encoder, label: Label, nonnull: Bool, throws: Bool) throws +} +extension ExCodable: EncodablePropertyWrapper where Value: Encodable { + fileprivate func encode(to encoder: Encoder, label: Label, nonnull: Bool, throws: Bool) throws { + if encode != nil { try encode!(encoder, wrappedValue) } + else { try encoder.encode(wrappedValue, for: stringKeys?.first ?? String(label), nonnull: self.nonnull ?? nonnull, throws: self.throws ?? `throws`) } + + } +} -// encode/decode -public extension ExCodable { - static var keyMapping: [KeyMap] { [] } // default implementation for optional property - func encode(to encoder: Encoder, with keyMapping: [KeyMap], nonnull: Bool = false, throws: Bool = false) throws { - try keyMapping.forEach { try $0.encode(self, encoder, nonnull, `throws`) } +fileprivate protocol DecodablePropertyWrapper { + func decode(from decoder: Decoder, label: Label, nonnull: Bool, throws: Bool) throws +} +extension ExCodable: DecodablePropertyWrapper where Value: Decodable { + fileprivate func decode(from decoder: Decoder, label: Label, nonnull: Bool, throws: Bool) throws { + if let value = decode != nil + ? try decode!(decoder) + : try decoder.decode(stringKeys ?? [String(label)], nonnull: self.nonnull ?? nonnull, throws: self.throws ?? `throws`) { + wrappedValue = value + } } - mutating func decode(from decoder: Decoder, with keyMapping: [KeyMap], nonnull: Bool = false, throws: Bool = false) throws { - try keyMapping.forEach { try $0.decode?(&self, decoder, nonnull, `throws`) } +} + +// MARK: Codable + +public extension Encodable { + func encode(to encoder: Encoder, nonnull: Bool, throws: Bool) throws { + var mirror: Mirror! = Mirror(reflecting: self) + while mirror != nil { + for child in mirror.children where child.label != nil { + try (child.value as? EncodablePropertyWrapper)?.encode(to: encoder, label: child.label!.dropFirst(), nonnull: false, throws: false) + } + mirror = mirror.superclassMirror + } } - func decodeReference(from decoder: Decoder, with keyMapping: [KeyMap], nonnull: Bool = false, throws: Bool = false) throws { - try keyMapping.forEach { try $0.decodeReference?(self, decoder, nonnull, `throws`) } +} + +public extension Decodable { + func decode(from decoder: Decoder, nonnull: Bool, throws: Bool) throws { + var mirror: Mirror! = Mirror(reflecting: self) + while mirror != nil { + for child in mirror.children where child.label != nil { + try (child.value as? DecodablePropertyWrapper)?.decode(from: decoder, label: child.label!.dropFirst(), nonnull: false, throws: false) + } + mirror = mirror.superclassMirror + } } } -public final class KeyMap { - fileprivate let encode: (_ root: Root, _ encoder: Encoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void - fileprivate let decode: ((_ root: inout Root, _ decoder: Decoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void)? - fileprivate let decodeReference: ((_ root: Root, _ decoder: Decoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void)? - private init(encode: @escaping (_ root: Root, _ encoder: Encoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void, - decode: ((_ root: inout Root, _ decoder: Decoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void)?, - decodeReference: ((_ root: Root, _ decoder: Decoder, _ nonnullAll: Bool, _ throwsAll: Bool) throws -> Void)?) { - (self.encode, self.decode, self.decodeReference) = (encode, decode, decodeReference) +// MARK: auto implementation for Encodable and Decodable + +public protocol ExAutoEncodable: Encodable {} +public extension ExAutoEncodable { + func encode(to encoder: Encoder) throws { + try encode(to: encoder, nonnull: false, throws: false) } } -public extension KeyMap { - convenience init(_ keyPath: WritableKeyPath, to codingKeys: String ..., nonnull: Bool? = nil, throws: Bool? = nil) { - self.init(encode: { root, encoder, nonnullAll, throwsAll in - try encoder.encode(root[keyPath: keyPath], for: codingKeys.first!, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) - }, decode: { root, decoder, nonnullAll, throwsAll in - if let value: Value = try decoder.decode(codingKeys, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) { - root[keyPath: keyPath] = value - } - }, decodeReference: nil) - } - convenience init(_ keyPath: WritableKeyPath, to codingKeys: Key ..., nonnull: Bool? = nil, throws: Bool? = nil) { - self.init(encode: { root, encoder, nonnullAll, throwsAll in - try encoder.encode(root[keyPath: keyPath], for: codingKeys.first!, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) - }, decode: { root, decoder, nonnullAll, throwsAll in - if let value: Value = try decoder.decode(codingKeys, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) { - root[keyPath: keyPath] = value - } - }, decodeReference: nil) - } - convenience init(ref keyPath: ReferenceWritableKeyPath, to codingKeys: String ..., nonnull: Bool? = nil, throws: Bool? = nil) { - self.init(encode: { root, encoder, nonnullAll, throwsAll in - try encoder.encode(root[keyPath: keyPath], for: codingKeys.first!, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) - }, decode: nil, decodeReference: { root, decoder, nonnullAll, throwsAll in - if let value: Value = try decoder.decode(codingKeys, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) { - root[keyPath: keyPath] = value - } - }) - } - convenience init(ref keyPath: ReferenceWritableKeyPath, to codingKeys: Key ..., nonnull: Bool? = nil, throws: Bool? = nil) { - self.init(encode: { root, encoder, nonnullAll, throwsAll in - try encoder.encode(root[keyPath: keyPath], for: codingKeys.first!, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) - }, decode: nil, decodeReference: { root, decoder, nonnullAll, throwsAll in - if let value: Value = try decoder.decode(codingKeys, nonnull: nonnull ?? nonnullAll, throws: `throws` ?? throwsAll) { - root[keyPath: keyPath] = value - } - }) +public protocol ExAutoDecodable: Decodable { init() } +public extension ExAutoDecodable { + init(from decoder: Decoder) throws { + self.init() + try decode(from: decoder, nonnull: false, throws: false) } } -// MARK: - subscript +public protocol ExAutoCodable: ExAutoEncodable, ExAutoDecodable {} + +// MARK: - Encoder&Decoder public extension Encoder { // , abortIfNull nonnull: Bool = false, abortOnError throws: Bool = false subscript(stringKey: String) -> T? { get { return nil } @@ -123,8 +141,6 @@ public extension Decoder { // , abortIfNull nonnull: Bool = false, abortOnError } } -// MARK: - Encoder&Decoder - public extension Encoder { func encodeNonnullThrows(_ value: T, for stringKey: String) throws { @@ -136,7 +152,7 @@ public extension Encoder { func encode(_ value: T?, for stringKey: String) { try? encode(value, for: stringKey, nonnull: false, throws: false) } - fileprivate func encode(_ value: T?, for stringKey: String, nonnull: Bool = false, throws: Bool = false) throws { + internal/* fileprivate */ func encode(_ value: T?, for stringKey: String, nonnull: Bool = false, throws: Bool = false) throws { let dot: Character = "." guard stringKey.contains(dot), stringKey.count > 1 else { @@ -167,7 +183,7 @@ public extension Encoder { func encode(_ value: T?, for codingKey: K) { try? encode(value, for: codingKey, nonnull: false, throws: false) } - fileprivate func encode(_ value: T?, for codingKey: K, nonnull: Bool = false, throws: Bool = false) throws { + internal/* fileprivate */ func encode(_ value: T?, for codingKey: K, nonnull: Bool = false, throws: Bool = false) throws { var container = self.container(keyedBy: K.self) do { if nonnull { try container.encode(value, forKey: codingKey) } @@ -197,7 +213,7 @@ public extension Decoder { func decode(_ stringKeys: [String], as type: T.Type = T.self) -> T? { return try? decode(stringKeys, as: type, nonnull: false, throws: false) } - fileprivate func decode(_ stringKeys: [String], as type: T.Type = T.self, nonnull: Bool = false, throws: Bool = false) throws -> T? { + internal/* fileprivate */ func decode(_ stringKeys: [String], as type: T.Type = T.self, nonnull: Bool = false, throws: Bool = false) throws -> T? { return try decode(stringKeys.map { ExCodingKey($0) }, as: type, nonnull: nonnull, throws: `throws`) } @@ -219,7 +235,7 @@ public extension Decoder { func decode(_ codingKeys: [K], as type: T.Type = T.self) -> T? { return try? decode(codingKeys, as: type, nonnull: false, throws: false) } - fileprivate func decode(_ codingKeys: [K], as type: T.Type = T.self, nonnull: Bool = false, throws: Bool = false) throws -> T? { + internal/* fileprivate */ func decode(_ codingKeys: [K], as type: T.Type = T.self, nonnull: Bool = false, throws: Bool = false) throws -> T? { do { let container = try self.container(keyedBy: K.self) return try container.decodeForAlternativeKeys(codingKeys, as: type, nonnull: nonnull, throws: `throws`) @@ -233,9 +249,7 @@ public extension Decoder { private struct ExCodingKey { public let stringValue: String, intValue: Int? - init(_ stringValue: String) { (self.stringValue, self.intValue) = (stringValue, nil) } - init(_ stringValue: Substring) { self.init(String(stringValue)) } - init(_ intValue: Int) { (self.intValue, self.stringValue) = (intValue, String(intValue)) } + init(_ stringValue: S) { (self.stringValue, self.intValue) = (stringValue as? String ?? String(stringValue), nil) } } extension ExCodingKey: CodingKey { @@ -245,7 +259,7 @@ extension ExCodingKey: CodingKey { // MARK: - alternative-keys + nested-keys + type-conversion -private extension KeyedDecodingContainer { +fileprivate extension KeyedDecodingContainer { func decodeForAlternativeKeys(_ codingKeys: [Self.Key], as type: T.Type = T.self, nonnull: Bool, throws: Bool) throws -> T? { @@ -422,13 +436,12 @@ public protocol ExCodableDecodingTypeConverter { func decode(_ container: KeyedDecodingContainer, codingKey: K, as type: T.Type) throws -> T? } -private var _decodingTypeConverters: [ExCodableDecodingTypeConverter] = [] +fileprivate var _decodingTypeConverters: [ExCodableDecodingTypeConverter] = [] public func register(_ decodingTypeConverter: ExCodableDecodingTypeConverter) { _decodingTypeConverters.append(decodingTypeConverter) } // MARK: - Encodable/Decodable -// - seealso: [Codextended](https://github.com/JohnSundell/Codextended) // Encodable.encode() -> Data? public extension Encodable { @@ -503,7 +516,7 @@ public protocol DataDecoder { extension JSONEncoder: DataEncoder {} extension JSONDecoder: DataDecoder {} -#if canImport(ObjectiveC) || swift(>=5.1) +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension PropertyListEncoder: DataEncoder {} +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension PropertyListDecoder: DataDecoder {} -#endif diff --git a/Tests/ExCodableTests/ExCodableTests.swift b/Tests/ExCodableTests/ExCodableTests.swift index ce7976d..90387eb 100644 --- a/Tests/ExCodableTests/ExCodableTests.swift +++ b/Tests/ExCodableTests/ExCodableTests.swift @@ -59,93 +59,52 @@ extension TestManualCodable: Codable { // MARK: struct struct TestStruct: Equatable { + @ExCodable("int") private(set) var int: Int = 0 - private(set) var string: String? + @ExCodable("string") + private(set) var string: String? = nil var bool: Bool! } - -extension TestStruct: ExCodable { - - static let keyMapping: [KeyMap] = [ - KeyMap(\.int, to: "int"), - KeyMap(\.string, to: "string"), - ] - - init(from decoder: Decoder) throws { - try decode(from: decoder, with: Self.keyMapping) - } - // `encode` with default implementation can be omitted - // func encode(to encoder: Encoder) throws { - // try encode(to: encoder, with: Self.keyMapping) - // } -} +extension TestStruct: ExAutoCodable {} // MARK: alternative-keys & alternative-keyMapping struct TestAlternativeKeys: Equatable { + @ExCodable("int", "i") var int: Int = 0 - var string: String! -} - -extension TestAlternativeKeys: ExCodable { - - static let keyMapping: [KeyMap] = [ - KeyMap(\.int, to: "int", "i"), - KeyMap(\.string, to: "string", "str", "s") - ] - - static let keyMappingFromLocal: [KeyMap] = [ - KeyMap(\.int, to: "INT"), - KeyMap(\.string, to: "STRING") - ] - - enum LocalKeys: String, CodingKey { - case isLocal = "_IS_LOCAL_" - } - - init(from decoder: Decoder) throws { - let isLocal = decoder[LocalKeys.isLocal] ?? false - try decode(from: decoder, with: isLocal ? Self.keyMappingFromLocal : Self.keyMapping) - } - func encode(to encoder: Encoder) throws { - try encode(to: encoder, with: Self.keyMappingFromLocal) - encoder[LocalKeys.isLocal] = true - } + @ExCodable("string", "str", "s") + var string: String! = nil } +extension TestAlternativeKeys: ExAutoCodable {} // MARK: nested-keys struct TestNestedKeys: Equatable { + @ExCodable var int: Int = 0 - var string: String! -} - -extension TestNestedKeys: ExCodable { - - static let keyMapping: [KeyMap] = [ - KeyMap(\.int, to: "int"), - KeyMap(\.string, to: "nested.string") - ] - - init(from decoder: Decoder) throws { - try decode(from: decoder, with: Self.keyMapping) - } - // func encode(to encoder: Encoder) throws { - // try encode(to: encoder, with: Self.keyMapping) - // } + @ExCodable("nested.string") + var string: String! = nil } +extension TestNestedKeys: ExAutoCodable {} // MARK: custom encode/decode struct TestCustomEncodeDecode: Equatable { + @ExCodable(Keys.int) var int: Int = 0 var string: String? + @ExCodable(encode: { encoder, value in + encoder[Keys.bool] = value + }, decode: { decoder in + return decoder[Keys.bool] + }) + var bool: Bool = false } -extension TestCustomEncodeDecode: ExCodable { +extension TestCustomEncodeDecode: Codable { private enum Keys: CodingKey { - case int, string + case int, string, bool } private static let dddd = "dddd" private func string(for int: Int) -> String { @@ -162,19 +121,15 @@ extension TestCustomEncodeDecode: ExCodable { } } - static let keyMapping: [KeyMap] = [ - KeyMap(\.int, to: Keys.int), - ] - init(from decoder: Decoder) throws { - try decode(from: decoder, with: Self.keyMapping) + try decode(from: decoder, nonnull: false, throws: false) string = decoder[Keys.string] if string == nil || string == Self.dddd { string = string(for: int) } } func encode(to encoder: Encoder) throws { - try encode(to: encoder, with: Self.keyMapping) + try encode(to: encoder, nonnull: false, throws: false) encoder[Keys.string] = Self.dddd } } @@ -309,44 +264,29 @@ struct FloatToBoolDecodingTypeConverter: ExCodableDecodingTypeConverter { } struct TestCustomTypeConverter: Equatable { + @ExCodable("doubleFromBool") var doubleFromBool: Double? = nil } - -extension TestCustomTypeConverter: ExCodable { - - static let keyMapping: [KeyMap] = [ - KeyMap(\.doubleFromBool, to: "doubleFromBool") - ] - - init(from decoder: Decoder) throws { - try decode(from: decoder, with: Self.keyMapping) - } - // func encode(to encoder: Encoder) throws { - // try encode(to: encoder, with: Self.keyMapping) - // } -} +extension TestCustomTypeConverter: ExAutoCodable {} // MARK: class -class TestClass: ExCodable, Equatable { +class TestClass: Codable, Equatable { + @ExCodable("int") var int: Int = 0 + @ExCodable("string") var string: String? = nil init(int: Int, string: String?) { (self.int, self.string) = (int, string) } - static let keyMapping: [KeyMap] = [ - KeyMap(ref: \.int, to: "int"), - KeyMap(ref: \.string, to: "string") - ] - required init(from decoder: Decoder) throws { - try decodeReference(from: decoder, with: Self.keyMapping) + try decode(from: decoder, nonnull: false, throws: false) + } + func encode(to encoder: Encoder) throws { + try encode(to: encoder, nonnull: false, throws: false) } - // func encode(to encoder: Encoder) throws { - // try encode(to: encoder, with: Self.keyMapping) - // } static func == (lhs: TestClass, rhs: TestClass) -> Bool { return lhs.int == rhs.int && lhs.string == rhs.string @@ -356,23 +296,16 @@ class TestClass: ExCodable, Equatable { // MARK: subclass class TestSubclass: TestClass { + + @ExCodable("bool") var bool: Bool = false required init(int: Int, string: String, bool: Bool) { self.bool = bool super.init(int: int, string: string) } - static let keyMappingForTestSubclass: [KeyMap] = [ - KeyMap(ref: \.bool, to: "bool") - ] - required init(from decoder: Decoder) throws { try super.init(from: decoder) - try decodeReference(from: decoder, with: Self.keyMappingForTestSubclass) - } - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - try encode(to: encoder, with: Self.keyMappingForTestSubclass) } static func == (lhs: TestSubclass, rhs: TestSubclass) -> Bool { @@ -385,24 +318,12 @@ class TestSubclass: TestClass { // MARK: ExCodable struct TestExCodable: Equatable { + @ExCodable("int") private(set) var int: Int = 0 - private(set) var string: String? -} - -extension TestExCodable: ExCodable { - - static let keyMapping: [KeyMap] = [ - KeyMap(\.int, to: "int"), - KeyMap(\.string, to: "string") - ] - - init(from decoder: Decoder) throws { - try decode(from: decoder, with: Self.keyMapping) - } - // func encode(to encoder: Encoder) throws { - // try encode(to: encoder, with: Self.keyMapping) - // } + @ExCodable("string") + private(set) var string: String? = nil } +extension TestExCodable: ExAutoCodable {} // MARK: - Tests @@ -456,9 +377,8 @@ final class ExCodableTests: XCTestCase { XCTAssertEqual(copy, test) let localJSON: [String: Any] = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] XCTAssertEqual(NSDictionary(dictionary: localJSON), [ - "_IS_LOCAL_": true, - "INT": 403, - "STRING": "Forbidden" + "int": 403, + "string": "Forbidden" ]) } else { @@ -486,7 +406,7 @@ final class ExCodableTests: XCTestCase { } func testCustomEncodeDecode() { - let test = TestCustomEncodeDecode(int: 418, string: "I'm a teapot") + let test = TestCustomEncodeDecode(int: 418, string: "I'm a teapot", bool: true) if let data = try? test.encoded() as Data, let copy = try? data.decoded() as TestCustomEncodeDecode { XCTAssertEqual(copy, test) @@ -494,7 +414,8 @@ final class ExCodableTests: XCTestCase { debugPrint(json) XCTAssertEqual(NSDictionary(dictionary: json), [ "int": 418, - "string": "dddd" + "string": "dddd", + "bool": true ]) } else { From 1fd4efa643b246585e663211e797301a273db2fb Mon Sep 17 00:00:00 2001 From: iwill Date: Fri, 2 Jul 2021 10:38:33 +0800 Subject: [PATCH 03/11] proj: enable build for develop --- .github/workflows/build-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1cfa351..7f64691 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: [ master ] + branches: [ master, develop ] pull_request: - branches: [ master ] + branches: [ master, develop ] workflow_dispatch: jobs: From 0f731352f6f1e482c3e6599698366ca45f0cb7d0 Mon Sep 17 00:00:00 2001 From: iwill Date: Fri, 2 Jul 2021 16:33:13 +0800 Subject: [PATCH 04/11] sty: coding style --- Sources/ExCodable/ExCodable+DEPRECATED.swift | 3 - Sources/ExCodable/ExCodable.swift | 64 +++++++++++--------- Tests/ExCodableTests/ExCodableTests.swift | 10 +-- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/Sources/ExCodable/ExCodable+DEPRECATED.swift b/Sources/ExCodable/ExCodable+DEPRECATED.swift index 7046def..ccb795f 100644 --- a/Sources/ExCodable/ExCodable+DEPRECATED.swift +++ b/Sources/ExCodable/ExCodable+DEPRECATED.swift @@ -18,16 +18,13 @@ public protocol ExCodableProtocol: Codable { @available(*, deprecated) public extension ExCodableProtocol where Root == Self { - // default implementation of ExCodableProtocol static var keyMapping: [KeyMap] { [] } - // default implementation of Encodable func encode(to encoder: Encoder) throws { try encode(to: encoder, with: Self.keyMapping) try encode(to: encoder, nonnull: false, throws: false) } - func decode(from decoder: Decoder) throws { try decode(from: decoder, nonnull: false, throws: false) } diff --git a/Sources/ExCodable/ExCodable.swift b/Sources/ExCodable/ExCodable.swift index c66f47c..95cf672 100644 --- a/Sources/ExCodable/ExCodable.swift +++ b/Sources/ExCodable/ExCodable.swift @@ -11,9 +11,15 @@ import Foundation /** * # ExCodable * - * A protocol extends `Encodable` & `Decodable` with `keyMapping` + * - `ExCodable`: A property-wrapper for mapping property to json-key. + * - `ExAutoEncodable` and `ExAutoDecodable`: Protocols with default implementation for Encodable & Decodable. + * - `ExAutoCodable`: A typealias for `ExAutoEncodable & ExAutoDecodable`. + * - Extensions of `Encodable & Decodable`, for encode/decode-ing from internal/external. + * - Extensions of `Encoder & Encoder`, for encode/decode-ing properties one-by-one. + * - Supports alternative-keys, nested-keys and type-conversion + * * <#swift#> <#codable#> <#json#> <#model#> <#type-inference#> - * <#key-mapping#> <#keypath#> <#codingkey#> <#subscript#> + * <#property-wrapper#> <#key-mapping#> <#codingkey#> <#subscript#> * <#alternative-keys#> <#nested-keys#> <#type-conversion#> * * - seealso: [Usage](https://github.com/iwill/ExCodable#usage) from GitGub @@ -70,7 +76,26 @@ extension ExCodable: DecodablePropertyWrapper where Value: Decodable { } } -// MARK: Codable +// MARK: - Auto implementation for Encodable & Decodable + +public protocol ExAutoEncodable: Encodable {} +public extension ExAutoEncodable { + func encode(to encoder: Encoder) throws { + try encode(to: encoder, nonnull: false, throws: false) + } +} + +public protocol ExAutoDecodable: Decodable { init() } +public extension ExAutoDecodable { + init(from decoder: Decoder) throws { + self.init() + try decode(from: decoder, nonnull: false, throws: false) + } +} + +public typealias ExAutoCodable = ExAutoEncodable & ExAutoDecodable + +// MARK: - Encodable & Decodable - internal public extension Encodable { func encode(to encoder: Encoder, nonnull: Bool, throws: Bool) throws { @@ -96,26 +121,7 @@ public extension Decodable { } } -// MARK: auto implementation for Encodable and Decodable - -public protocol ExAutoEncodable: Encodable {} -public extension ExAutoEncodable { - func encode(to encoder: Encoder) throws { - try encode(to: encoder, nonnull: false, throws: false) - } -} - -public protocol ExAutoDecodable: Decodable { init() } -public extension ExAutoDecodable { - init(from decoder: Decoder) throws { - self.init() - try decode(from: decoder, nonnull: false, throws: false) - } -} - -public protocol ExAutoCodable: ExAutoEncodable, ExAutoDecodable {} - -// MARK: - Encoder&Decoder +// MARK: - Encoder & Decoder public extension Encoder { // , abortIfNull nonnull: Bool = false, abortOnError throws: Bool = false subscript(stringKey: String) -> T? { get { return nil } @@ -257,7 +263,7 @@ extension ExCodingKey: CodingKey { public init?(intValue: Int) { self.init(intValue) } } -// MARK: - alternative-keys + nested-keys + type-conversion +// MARK: - KeyedDecodingContainer - alternative-keys + nested-keys + type-conversion fileprivate extension KeyedDecodingContainer { @@ -418,13 +424,13 @@ fileprivate extension KeyedDecodingContainer { else if let double = try? decodeIfPresent(Double.self, forKey: codingKey) { return String(describing: double) as? T } // include Float } - for conversion in _decodingTypeConverters { - if let value = try? conversion.decode(self, codingKey: codingKey, as: type) { + for converter in _decodingTypeConverters { + if let value = try? converter.decode(self, codingKey: codingKey, as: type) { return value } } - if let custom = self as? ExCodableDecodingTypeConverter, - let value = try? custom.decode(self, codingKey: codingKey, as: type) { + if let customConverter = self as? ExCodableDecodingTypeConverter, + let value = try? customConverter.decode(self, codingKey: codingKey, as: type) { return value } @@ -441,7 +447,7 @@ public func register(_ decodingTypeConverter: ExCodableDecodingTypeConverter) { _decodingTypeConverters.append(decodingTypeConverter) } -// MARK: - Encodable/Decodable +// MARK: - Encodable & Decodable - external // Encodable.encode() -> Data? public extension Encodable { diff --git a/Tests/ExCodableTests/ExCodableTests.swift b/Tests/ExCodableTests/ExCodableTests.swift index 90387eb..2bbf9b1 100644 --- a/Tests/ExCodableTests/ExCodableTests.swift +++ b/Tests/ExCodableTests/ExCodableTests.swift @@ -90,14 +90,14 @@ extension TestNestedKeys: ExAutoCodable {} // MARK: custom encode/decode struct TestCustomEncodeDecode: Equatable { + @ExCodable(Keys.int) var int: Int = 0 + var string: String? - @ExCodable(encode: { encoder, value in - encoder[Keys.bool] = value - }, decode: { decoder in - return decoder[Keys.bool] - }) + + @ExCodable(encode: { encoder, value in encoder[Keys.bool] = value }, + decode: { decoder in return decoder[Keys.bool] }) var bool: Bool = false } From f5003090c522e7a4f0fbf60cc67e930e426899c3 Mon Sep 17 00:00:00 2001 From: "Mr. Ming" Date: Mon, 16 Aug 2021 16:56:19 +0800 Subject: [PATCH 05/11] sty: comments & readme --- README.md | 7 +++++-- Sources/ExCodable/ExCodable.swift | 23 +++++++++++------------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index fdb81b7..ce4039f 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,12 @@ [![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg)](https://swift.org/) [![Swift Package Manager](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager/) [![Platforms](https://img.shields.io/cocoapods/p/ExCodable.svg)](#readme) +
[![Build and Test](https://github.com/iwill/ExCodable/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/iwill/ExCodable/actions/workflows/build-and-test.yml) [![GitHub Releases (latest SemVer)](https://img.shields.io/github/v/release/iwill/ExCodable.svg?sort=semver)](https://github.com/iwill/ExCodable/releases) [![Deploy to CocoaPods](https://github.com/iwill/ExCodable/actions/workflows/deploy_to_cocoapods.yml/badge.svg)](https://github.com/iwill/ExCodable/actions/workflows/deploy_to_cocoapods.yml) [![Cocoapods](https://img.shields.io/cocoapods/v/ExCodable.svg)](https://cocoapods.org/pods/ExCodable) +
[![LICENSE](https://img.shields.io/github/license/iwill/ExCodable.svg)](https://github.com/iwill/ExCodable/blob/master/LICENSE) [![@minglq](https://img.shields.io/twitter/url?url=https%3A%2F%2Fgithub.com%2Fiwill%2FExCodable)](https://twitter.com/minglq) @@ -17,6 +19,7 @@ En | [中文](https://iwill.im/ExCodable/) - [Features](#features) - [Usage](#usage) - [Requirements](#requirements) +- [Migration Guides](#migration-guides) - [Installation](#installation) - [Credits](#credits) - [License](#license) @@ -24,7 +27,7 @@ En | [中文](https://iwill.im/ExCodable/) ## Features - Extends Swift `Codable` - `Encodable & Decodable`; -- Supports Key-Mapping via `KeyPath` and Coding-Key: +- Supports Key-Mapping via Property-Wrapper `ExCodable` + `String`: - `ExCodable` did not read/write memory via unsafe pointers; - No need to encode/decode properties one by one; - Just requires using `var` to declare properties and provide default values; @@ -57,7 +60,7 @@ struct TestAutoCodable: Codable, Equatable { ``` -But, if you have to encode/decode manually for some reason, e.g. Alternative-Keys and Nested-Keys ... +But, if you have to encode/decode manually for some reason, e.g. Default-Value, Alternative-Keys, Nested-Keys or Type-Conversions ... ```swift struct TestManualCodable: Equatable { diff --git a/Sources/ExCodable/ExCodable.swift b/Sources/ExCodable/ExCodable.swift index 95cf672..83886d1 100644 --- a/Sources/ExCodable/ExCodable.swift +++ b/Sources/ExCodable/ExCodable.swift @@ -11,20 +11,20 @@ import Foundation /** * # ExCodable * - * - `ExCodable`: A property-wrapper for mapping property to json-key. - * - `ExAutoEncodable` and `ExAutoDecodable`: Protocols with default implementation for Encodable & Decodable. + * - `ExCodable`: A property-wrapper for mapping properties to JSON keys. + * - `ExAutoEncodable` & `ExAutoDecodable`: Protocols with default implementation for Encodable & Decodable. * - `ExAutoCodable`: A typealias for `ExAutoEncodable & ExAutoDecodable`. - * - Extensions of `Encodable & Decodable`, for encode/decode-ing from internal/external. - * - Extensions of `Encoder & Encoder`, for encode/decode-ing properties one-by-one. - * - Supports alternative-keys, nested-keys and type-conversion + * - `Encodable` & `Decodable` extensions for encode/decode-ing from internal/external. + * - `Encoder` & `Encoder` extensions for encode/decode-ing properties one by one. + * - Supports Alternative-Keys, Nested-Keys, Type-Conversions and Default-Values. * * <#swift#> <#codable#> <#json#> <#model#> <#type-inference#> - * <#property-wrapper#> <#key-mapping#> <#codingkey#> <#subscript#> - * <#alternative-keys#> <#nested-keys#> <#type-conversion#> + * <#key-mapping#> <#property-wrapper#> <#coding-key#> <#subscript#> + * <#alternative-keys#> <#nested-keys#> <#type-conversions#> * - * - seealso: [Usage](https://github.com/iwill/ExCodable#usage) from GitGub - * - seealso: `ExCodableTests.swift` form the source code - * - seealso: Idea from [Decoding and overriding](https://www.swiftbysundell.com/articles/property-wrappers-in-swift/#decoding-and-overriding), by John Sundell. + * - seealso: [Usage](https://github.com/iwill/ExCodable#usage) from the `README.md` + * - seealso: `ExCodableTests.swift` from the `Tests` + * - seealso: [Decoding and overriding](https://www.swiftbysundell.com/articles/property-wrappers-in-swift/#decoding-and-overriding) and [Useful Codable extensions](https://www.swiftbysundell.com/tips/useful-codable-extensions/), by John Sundell. */ @propertyWrapper @@ -59,7 +59,6 @@ extension ExCodable: EncodablePropertyWrapper where Value: Encodable { fileprivate func encode(to encoder: Encoder, label: Label, nonnull: Bool, throws: Bool) throws { if encode != nil { try encode!(encoder, wrappedValue) } else { try encoder.encode(wrappedValue, for: stringKeys?.first ?? String(label), nonnull: self.nonnull ?? nonnull, throws: self.throws ?? `throws`) } - } } @@ -263,7 +262,7 @@ extension ExCodingKey: CodingKey { public init?(intValue: Int) { self.init(intValue) } } -// MARK: - KeyedDecodingContainer - alternative-keys + nested-keys + type-conversion +// MARK: - KeyedDecodingContainer - alternative-keys + nested-keys + type-conversions fileprivate extension KeyedDecodingContainer { From 239a0ea84a4700859d4f656a4454607ea72342d3 Mon Sep 17 00:00:00 2001 From: iwill Date: Fri, 4 Feb 2022 15:23:37 +0800 Subject: [PATCH 06/11] opt: type inference in test --- Tests/ExCodableTests/ExCodableTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/ExCodableTests/ExCodableTests.swift b/Tests/ExCodableTests/ExCodableTests.swift index 2bbf9b1..7d7cfbc 100644 --- a/Tests/ExCodableTests/ExCodableTests.swift +++ b/Tests/ExCodableTests/ExCodableTests.swift @@ -333,7 +333,7 @@ final class ExCodableTests: XCTestCase { let test = TestAutoCodable(int: 100, string: "Continue") if let data = try? test.encoded() as Data, let copy = try? data.decoded() as TestAutoCodable, - let json: [String: Any] = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { XCTAssertEqual(copy, test) XCTAssertEqual(NSDictionary(dictionary: json), ["i": 100, "s": "Continue"]) } @@ -375,7 +375,7 @@ final class ExCodableTests: XCTestCase { let copy = try? data.decoded() as TestAlternativeKeys { XCTAssertEqual(test, TestAlternativeKeys(int: 403, string: "Forbidden")) XCTAssertEqual(copy, test) - let localJSON: [String: Any] = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] + let localJSON = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] XCTAssertEqual(NSDictionary(dictionary: localJSON), [ "int": 403, "string": "Forbidden" @@ -391,7 +391,7 @@ final class ExCodableTests: XCTestCase { if let data = try? test.encoded() as Data, let copy = try? data.decoded() as TestNestedKeys { XCTAssertEqual(copy, test) - let json: [String: Any] = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] + let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] debugPrint(json) XCTAssertEqual(NSDictionary(dictionary: json), [ "int": 404, @@ -410,7 +410,7 @@ final class ExCodableTests: XCTestCase { if let data = try? test.encoded() as Data, let copy = try? data.decoded() as TestCustomEncodeDecode { XCTAssertEqual(copy, test) - let json: [String: Any] = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] + let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:] debugPrint(json) XCTAssertEqual(NSDictionary(dictionary: json), [ "int": 418, From eb938524b5ec0bc5fb222b357e830a95c85d217f Mon Sep 17 00:00:00 2001 From: iwill Date: Fri, 4 Mar 2022 01:05:30 +0800 Subject: [PATCH 07/11] opt: test --- Tests/ExCodableTests/ExCodableTests.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/ExCodableTests/ExCodableTests.swift b/Tests/ExCodableTests/ExCodableTests.swift index 7d7cfbc..637744c 100644 --- a/Tests/ExCodableTests/ExCodableTests.swift +++ b/Tests/ExCodableTests/ExCodableTests.swift @@ -82,7 +82,7 @@ extension TestAlternativeKeys: ExAutoCodable {} struct TestNestedKeys: Equatable { @ExCodable var int: Int = 0 - @ExCodable("nested.string") + @ExCodable("nested.nested.string") var string: String! = nil } extension TestNestedKeys: ExAutoCodable {} @@ -396,7 +396,9 @@ final class ExCodableTests: XCTestCase { XCTAssertEqual(NSDictionary(dictionary: json), [ "int": 404, "nested": [ - "string": "Not Found" + "nested": [ + "string": "Not Found" + ] ] ]) } From fdbf4214e31b91dea3fea531ecb53cc3af3222be Mon Sep 17 00:00:00 2001 From: iwill Date: Fri, 4 Mar 2022 01:07:37 +0800 Subject: [PATCH 08/11] =?UTF-8?q?opt:=20Mr.=20Ming=20>=20Mr.=20M=C3=ADng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ExCodable.podspec | 2 +- LICENSE | 2 +- README.md | 2 +- Sources/ExCodable/ExCodable+DEPRECATED.swift | 4 ++-- Sources/ExCodable/ExCodable.swift | 4 ++-- Tests/ExCodableTests/ExCodableTests.swift | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ExCodable.podspec b/ExCodable.podspec index fa839ab..336bd9c 100644 --- a/ExCodable.podspec +++ b/ExCodable.podspec @@ -10,7 +10,7 @@ Pod::Spec.new do |s| # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # s.license = "MIT" - s.author = { "Mr. Ming" => "i+ExCodable@iwill.im" } + s.author = { "Mr. Míng" => "i+ExCodable@iwill.im" } s.social_media_url = "https://iwill.im/about/" # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # diff --git a/LICENSE b/LICENSE index f66b212..2c34e9a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Mr. Ming +Copyright (c) 2021 Mr. Míng Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ce4039f..699e31f 100644 --- a/README.md +++ b/README.md @@ -417,7 +417,7 @@ pod 'ExCodable', '~> 0.5.0' - John Sundell ([@JohnSundell](https://github.com/JohnSundell)) and the ideas from his [Codextended](https://github.com/JohnSundell/Codextended) - ibireme ([@ibireme](https://github.com/ibireme)) and the features from his [YYModel](https://github.com/ibireme/YYModel) -- Mr. Ming ([@iwill](https://github.com/iwill)) | i+ExCodable@iwill.im +- Mr. Míng ([@iwill](https://github.com/iwill)) | i+ExCodable@iwill.im ## License diff --git a/Sources/ExCodable/ExCodable+DEPRECATED.swift b/Sources/ExCodable/ExCodable+DEPRECATED.swift index ccb795f..8efb99b 100644 --- a/Sources/ExCodable/ExCodable+DEPRECATED.swift +++ b/Sources/ExCodable/ExCodable+DEPRECATED.swift @@ -2,8 +2,8 @@ // ExCodable.swift // ExCodable // -// Created by Mr. Ming on 2021-07-01. -// Copyright (c) 2021 Mr. Ming . Released under the MIT license. +// Created by Mr. Míng on 2021-07-01. +// Copyright (c) 2021 Mr. Míng . Released under the MIT license. // import Foundation diff --git a/Sources/ExCodable/ExCodable.swift b/Sources/ExCodable/ExCodable.swift index 83886d1..0b7de5b 100644 --- a/Sources/ExCodable/ExCodable.swift +++ b/Sources/ExCodable/ExCodable.swift @@ -2,8 +2,8 @@ // ExCodable.swift // ExCodable // -// Created by Mr. Ming on 2021-02-10. -// Copyright (c) 2021 Mr. Ming . Released under the MIT license. +// Created by Mr. Míng on 2021-02-10. +// Copyright (c) 2021 Mr. Míng . Released under the MIT license. // import Foundation diff --git a/Tests/ExCodableTests/ExCodableTests.swift b/Tests/ExCodableTests/ExCodableTests.swift index 637744c..701cb11 100644 --- a/Tests/ExCodableTests/ExCodableTests.swift +++ b/Tests/ExCodableTests/ExCodableTests.swift @@ -2,8 +2,8 @@ // ExCodableTests.swift // ExCodable // -// Created by Mr. Ming on 2021-02-10. -// Copyright (c) 2021 Mr. Ming . Released under the MIT license. +// Created by Mr. Míng on 2021-02-10. +// Copyright (c) 2021 Mr. Míng . Released under the MIT license. // import XCTest From fe0ec0dce017b7de02e10d1fddd3069534fb351b Mon Sep 17 00:00:00 2001 From: iwill Date: Fri, 4 Mar 2022 01:08:41 +0800 Subject: [PATCH 09/11] proj: copyright --- LICENSE | 2 +- Sources/ExCodable/ExCodable+DEPRECATED.swift | 2 +- Sources/ExCodable/ExCodable.swift | 2 +- Tests/ExCodableTests/ExCodableTests.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 2c34e9a..d4a2b48 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Mr. Míng +Copyright (c) 2022 Mr. Míng Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Sources/ExCodable/ExCodable+DEPRECATED.swift b/Sources/ExCodable/ExCodable+DEPRECATED.swift index 8efb99b..4cd2657 100644 --- a/Sources/ExCodable/ExCodable+DEPRECATED.swift +++ b/Sources/ExCodable/ExCodable+DEPRECATED.swift @@ -3,7 +3,7 @@ // ExCodable // // Created by Mr. Míng on 2021-07-01. -// Copyright (c) 2021 Mr. Míng . Released under the MIT license. +// Copyright (c) 2022 Mr. Míng . Released under the MIT license. // import Foundation diff --git a/Sources/ExCodable/ExCodable.swift b/Sources/ExCodable/ExCodable.swift index 0b7de5b..fb46e37 100644 --- a/Sources/ExCodable/ExCodable.swift +++ b/Sources/ExCodable/ExCodable.swift @@ -3,7 +3,7 @@ // ExCodable // // Created by Mr. Míng on 2021-02-10. -// Copyright (c) 2021 Mr. Míng . Released under the MIT license. +// Copyright (c) 2022 Mr. Míng . Released under the MIT license. // import Foundation diff --git a/Tests/ExCodableTests/ExCodableTests.swift b/Tests/ExCodableTests/ExCodableTests.swift index 701cb11..d9ee993 100644 --- a/Tests/ExCodableTests/ExCodableTests.swift +++ b/Tests/ExCodableTests/ExCodableTests.swift @@ -3,7 +3,7 @@ // ExCodable // // Created by Mr. Míng on 2021-02-10. -// Copyright (c) 2021 Mr. Míng . Released under the MIT license. +// Copyright (c) 2022 Mr. Míng . Released under the MIT license. // import XCTest From fb605c4248e9886a68ba55692d97cb0fc3add953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=ADng?= Date: Wed, 1 Jun 2022 15:38:09 +0800 Subject: [PATCH 10/11] fix: #5 print `wrappedValue` instead of `ExCodable` --- Sources/ExCodable/ExCodable.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/ExCodable/ExCodable.swift b/Sources/ExCodable/ExCodable.swift index fb46e37..36959a5 100644 --- a/Sources/ExCodable/ExCodable.swift +++ b/Sources/ExCodable/ExCodable.swift @@ -51,6 +51,10 @@ extension ExCodable: Equatable where Value: Equatable { return lhs.wrappedValue == rhs.wrappedValue } } +extension ExCodable: CustomStringConvertible { // CustomDebugStringConvertible + public var description: String { String(describing: wrappedValue) } + // public var debugDescription: String { "\(type(of: self))(\(wrappedValue))" } +} fileprivate protocol EncodablePropertyWrapper { func encode(to encoder: Encoder, label: Label, nonnull: Bool, throws: Bool) throws From 4780fb8fb3728b0f6ef98116560b8d4b892eb55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=99=E6=97=AD=E6=B4=AA?= Date: Tue, 22 Nov 2022 13:45:48 +0800 Subject: [PATCH 11/11] feat: make `Any` support Codable, like: [String: Any], [Any] --- .../DecodingContainer+AnyCollection.swift | 151 ++++++++++++++++++ .../EncodingContainer+AnyCollection.swift | 131 +++++++++++++++ Sources/ExCodable/ExCodable.swift | 61 ++++++- 3 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 Sources/ExCodable/DecodingContainer+AnyCollection.swift create mode 100644 Sources/ExCodable/EncodingContainer+AnyCollection.swift diff --git a/Sources/ExCodable/DecodingContainer+AnyCollection.swift b/Sources/ExCodable/DecodingContainer+AnyCollection.swift new file mode 100644 index 0000000..1be33d6 --- /dev/null +++ b/Sources/ExCodable/DecodingContainer+AnyCollection.swift @@ -0,0 +1,151 @@ +// +// DecodingContainer+AnyCollection.swift +// AnyDecodable +// +// Created by levantAJ on 1/18/19. +// Copyright © 2019 levantAJ. All rights reserved. +// +// https://github.com/levantAJ/AnyCodable +import Foundation + +struct AnyCodingKey: CodingKey { + var stringValue: String + var intValue: Int? + + init?(stringValue: String) { + self.stringValue = stringValue + } + + init?(intValue: Int) { + self.intValue = intValue + self.stringValue = String(intValue) + } +} + +extension KeyedDecodingContainer { + /// Decodes a value of the given type for the given key. + /// + /// - parameter type: The type of value to decode. + /// - parameter key: The key that the decoded value is associated with. + /// - returns: A value of the requested type, if present for the given key + /// and convertible to the requested type. + /// - throws: `DecodingError.typeMismatch` if the encountered encoded value + /// is not convertible to the requested type. + /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry + /// for the given key. + /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for + /// the given key. + public func decode(_ type: [Any].Type, forKey key: KeyedDecodingContainer.Key) throws -> [Any] { + var values = try nestedUnkeyedContainer(forKey: key) + return try values.decode(type) + } + + /// Decodes a value of the given type for the given key. + /// + /// - parameter type: The type of value to decode. + /// - parameter key: The key that the decoded value is associated with. + /// - returns: A value of the requested type, if present for the given key + /// and convertible to the requested type. + /// - throws: `DecodingError.typeMismatch` if the encountered encoded value + /// is not convertible to the requested type. + /// - throws: `DecodingError.keyNotFound` if `self` does not have an entry + /// for the given key. + /// - throws: `DecodingError.valueNotFound` if `self` has a null entry for + /// the given key. + public func decode(_ type: [String: Any].Type, forKey key: KeyedDecodingContainer.Key) throws -> [String: Any] { + let values = try nestedContainer(keyedBy: AnyCodingKey.self, forKey: key) + return try values.decode(type) + } + + /// Decodes a value of the given type for the given key, if present. + /// + /// This method returns `nil` if the container does not have a value + /// associated with `key`, or if the value is null. The difference between + /// these states can be distinguished with a `contains(_:)` call. + /// + /// - parameter type: The type of value to decode. + /// - parameter key: The key that the decoded value is associated with. + /// - returns: A decoded value of the requested type, or `nil` if the + /// `Decoder` does not have an entry associated with the given key, or if + /// the value is a null value. + /// - throws: `DecodingError.typeMismatch` if the encountered encoded value + /// is not convertible to the requested type. + public func decodeIfPresent(_ type: [Any].Type, forKey key: KeyedDecodingContainer.Key) throws -> [Any]? { + guard contains(key), + try decodeNil(forKey: key) == false else { return nil } + return try decode(type, forKey: key) + } + + /// Decodes a value of the given type for the given key, if present. + /// + /// This method returns `nil` if the container does not have a value + /// associated with `key`, or if the value is null. The difference between + /// these states can be distinguished with a `contains(_:)` call. + /// + /// - parameter type: The type of value to decode. + /// - parameter key: The key that the decoded value is associated with. + /// - returns: A decoded value of the requested type, or `nil` if the + /// `Decoder` does not have an entry associated with the given key, or if + /// the value is a null value. + /// - throws: `DecodingError.typeMismatch` if the encountered encoded value + /// is not convertible to the requested type. + public func decodeIfPresent(_ type: [String: Any].Type, forKey key: KeyedDecodingContainer.Key) throws -> [String: Any]? { + guard contains(key), + try decodeNil(forKey: key) == false else { return nil } + return try decode(type, forKey: key) + } +} + +private extension KeyedDecodingContainer { + func decode(_ type: [String: Any].Type) throws -> [String: Any] { + var dictionary: [String: Any] = [:] + for key in allKeys { + if try decodeNil(forKey: key) { + dictionary[key.stringValue] = NSNull() + } else if let bool = try? decode(Bool.self, forKey: key) { + dictionary[key.stringValue] = bool + } else if let string = try? decode(String.self, forKey: key) { + dictionary[key.stringValue] = string + } else if let int = try? decode(Int.self, forKey: key) { + dictionary[key.stringValue] = int + } else if let double = try? decode(Double.self, forKey: key) { + dictionary[key.stringValue] = double + } else if let dict = try? decode([String: Any].self, forKey: key) { + dictionary[key.stringValue] = dict + } else if let array = try? decode([Any].self, forKey: key) { + dictionary[key.stringValue] = array + } + } + return dictionary + } +} + +private extension UnkeyedDecodingContainer { + mutating func decode(_ type: [Any].Type) throws -> [Any] { + var elements: [Any] = [] + while !isAtEnd { + if try decodeNil() { + elements.append(NSNull()) + } else if let int = try? decode(Int.self) { + elements.append(int) + } else if let bool = try? decode(Bool.self) { + elements.append(bool) + } else if let double = try? decode(Double.self) { + elements.append(double) + } else if let string = try? decode(String.self) { + elements.append(string) + } else if let values = try? nestedContainer(keyedBy: AnyCodingKey.self), + let element = try? values.decode([String: Any].self) { + elements.append(element) + } else if var values = try? nestedUnkeyedContainer(), + let element = try? values.decode([Any].self) { + elements.append(element) + } + } + return elements + } + mutating func decode(_ type: Dictionary.Type) throws -> Dictionary { + let nestedContainer = try self.nestedContainer(keyedBy: AnyCodingKey.self) + return try nestedContainer.decode(type) + } +} diff --git a/Sources/ExCodable/EncodingContainer+AnyCollection.swift b/Sources/ExCodable/EncodingContainer+AnyCollection.swift new file mode 100644 index 0000000..faf1e01 --- /dev/null +++ b/Sources/ExCodable/EncodingContainer+AnyCollection.swift @@ -0,0 +1,131 @@ +// +// EncodingContainer+AnyCollection.swift +// AnyDecodable +// +// Created by ShopBack on 1/19/19. +// Copyright © 2019 levantAJ. All rights reserved. +// +// https://github.com/levantAJ/AnyCodable +import Foundation + +extension KeyedEncodingContainer { + /// Encodes the given value for the given key. + /// + /// - parameter value: The value to encode. + /// - parameter key: The key to associate the value with. + /// - throws: `EncodingError.invalidValue` if the given value is invalid in + /// the current context for this format. + public mutating func encode(_ value: [String: Any], forKey key: KeyedEncodingContainer.Key) throws { + var container = nestedContainer(keyedBy: AnyCodingKey.self, forKey: key) + try container.encode(value) + } + + /// Encodes the given value for the given key. + /// + /// - parameter value: The value to encode. + /// - parameter key: The key to associate the value with. + /// - throws: `EncodingError.invalidValue` if the given value is invalid in + /// the current context for this format. + public mutating func encode(_ value: [Any], forKey key: KeyedEncodingContainer.Key) throws { + var container = nestedUnkeyedContainer(forKey: key) + try container.encode(value) + } + + /// Encodes the given value for the given key if it is not `nil`. + /// + /// - parameter value: The value to encode. + /// - parameter key: The key to associate the value with. + /// - throws: `EncodingError.invalidValue` if the given value is invalid in + /// the current context for this format. + public mutating func encodeIfPresent(_ value: [String: Any]?, forKey key: KeyedEncodingContainer.Key) throws { + if let value = value { + var container = nestedContainer(keyedBy: AnyCodingKey.self, forKey: key) + try container.encode(value) + } else { + try encodeNil(forKey: key) + } + } + + /// Encodes the given value for the given key if it is not `nil`. + /// + /// - parameter value: The value to encode. + /// - parameter key: The key to associate the value with. + /// - throws: `EncodingError.invalidValue` if the given value is invalid in + /// the current context for this format. + public mutating func encodeIfPresent(_ value: [Any]?, forKey key: KeyedEncodingContainer.Key) throws { + if let value = value { + var container = nestedUnkeyedContainer(forKey: key) + try container.encode(value) + } else { + try encodeNil(forKey: key) + } + } +} + +private extension KeyedEncodingContainer where K == AnyCodingKey { + mutating func encode(_ value: [String: Any]) throws { + for (k, v) in value { + let key = AnyCodingKey(stringValue: k)! + switch v { + case is NSNull: + try encodeNil(forKey: key) + case let string as String: + try encode(string, forKey: key) + case let int as Int: + try encode(int, forKey: key) + case let bool as Bool: + try encode(bool, forKey: key) + case let double as Double: + try encode(double, forKey: key) + case let dict as [String: Any]: + try encode(dict, forKey: key) + case let array as [Any]: + try encode(array, forKey: key) + default: + debugPrint("⚠️ Unsuported type!", v) + continue + } + } + } +} + +private extension UnkeyedEncodingContainer { + /// Encodes the given value. + /// + /// - parameter value: The value to encode. + /// - throws: `EncodingError.invalidValue` if the given value is invalid in + /// the current context for this format. + mutating func encode(_ value: [Any]) throws { + for v in value { + switch v { + case is NSNull: + try encodeNil() + case let string as String: + try encode(string) + case let int as Int: + try encode(int) + case let bool as Bool: + try encode(bool) + case let double as Double: + try encode(double) + case let dict as [String: Any]: + try encode(dict) + case let array as [Any]: + var values = nestedUnkeyedContainer() + try values.encode(array) + default: + debugPrint("⚠️ Unsuported type!", v) + } + } + } + + /// Encodes the given value. + /// + /// - parameter value: The value to encode. + /// - throws: `EncodingError.invalidValue` if the given value is invalid in + /// the current context for this format. + mutating func encode(_ value: [String: Any]) throws { + var container = self.nestedContainer(keyedBy: AnyCodingKey.self) + try container.encode(value) + } +} diff --git a/Sources/ExCodable/ExCodable.swift b/Sources/ExCodable/ExCodable.swift index 36959a5..f4c63f9 100644 --- a/Sources/ExCodable/ExCodable.swift +++ b/Sources/ExCodable/ExCodable.swift @@ -79,6 +79,55 @@ extension ExCodable: DecodablePropertyWrapper where Value: Decodable { } } +// Make `Any` support Codable, like: [String: Any], [Any] +fileprivate protocol EncodableAnyPropertyWrapper { + func encode(to encoder: Encoder, label: Label, nonnull: Bool, throws: Bool) throws +} +extension ExCodable: EncodableAnyPropertyWrapper { + fileprivate func encode(to encoder: Encoder, label: Label, nonnull: Bool, throws: Bool) throws { + if encode != nil { try encode!(encoder, wrappedValue) } + else { + let t = type(of: wrappedValue) + if let key = AnyCodingKey(stringValue: String(label)) { + if (t is [String: Any].Type || t is [String: Any?].Type || t is [String: Any]?.Type || t is [String: Any?]?.Type) { + var container = try encoder.container(keyedBy: AnyCodingKey.self) + try container.encodeIfPresent(wrappedValue as? [String: Any], forKey: key) + } else if (t is [Any].Type || t is [Any?].Type || t is [Any]?.Type || t is [Any?]?.Type) { + var container = try encoder.container(keyedBy: AnyCodingKey.self) + try container.encodeIfPresent(wrappedValue as? [Any], forKey: key) + } + } + } + } +} +fileprivate protocol DecodableAnyPropertyWrapper { + func decode(from decoder: Decoder, label: Label, nonnull: Bool, throws: Bool) throws +} +extension ExCodable: DecodableAnyPropertyWrapper { + fileprivate func decode(from decoder: Decoder, label: Label, nonnull: Bool, throws: Bool) throws { + if let decode = decode { + if let value = try decode(decoder) { + wrappedValue = value + } + } else { + let t = type(of: wrappedValue) + if let key = AnyCodingKey(stringValue: String(label)) { + if (t is [String: Any].Type || t is [String: Any?].Type || t is [String: Any]?.Type || t is [String: Any?]?.Type) { + let container = try decoder.container(keyedBy: AnyCodingKey.self) + if let value = try container.decodeIfPresent([String: Any].self, forKey: key) as? Value { + wrappedValue = value + } + } else if (t is [Any].Type || t is [Any?].Type || t is [Any]?.Type || t is [Any?]?.Type) { + let container = try decoder.container(keyedBy: AnyCodingKey.self) + if let value = try container.decodeIfPresent([Any].self, forKey: key) as? Value { + wrappedValue = value + } + } + } + } + } +} + // MARK: - Auto implementation for Encodable & Decodable public protocol ExAutoEncodable: Encodable {} @@ -105,7 +154,11 @@ public extension Encodable { var mirror: Mirror! = Mirror(reflecting: self) while mirror != nil { for child in mirror.children where child.label != nil { - try (child.value as? EncodablePropertyWrapper)?.encode(to: encoder, label: child.label!.dropFirst(), nonnull: false, throws: false) + if let wrapper = (child.value as? EncodablePropertyWrapper) { + try wrapper.encode(to: encoder, label: child.label!.dropFirst(), nonnull: false, throws: false) + } else { + try (child.value as? EncodableAnyPropertyWrapper)?.encode(to: encoder, label: child.label!.dropFirst(), nonnull: false, throws: false) + } } mirror = mirror.superclassMirror } @@ -117,7 +170,11 @@ public extension Decodable { var mirror: Mirror! = Mirror(reflecting: self) while mirror != nil { for child in mirror.children where child.label != nil { - try (child.value as? DecodablePropertyWrapper)?.decode(from: decoder, label: child.label!.dropFirst(), nonnull: false, throws: false) + if let wrapper = (child.value as? DecodablePropertyWrapper) { + try wrapper.decode(from: decoder, label: child.label!.dropFirst(), nonnull: false, throws: false) + } else { + try (child.value as? DecodableAnyPropertyWrapper)?.decode(from: decoder, label: child.label!.dropFirst(), nonnull: false, throws: false) + } } mirror = mirror.superclassMirror }