From dea607c4a7f8963b87797f0346ed14e486690ef8 Mon Sep 17 00:00:00 2001 From: Michel Fortin Date: Thu, 20 Feb 2020 11:54:59 -0500 Subject: [PATCH] Added allowIntKey mode to encode / decode keys as integers in dictionaries when the CodingKey implementation provides an intValue. --- Sources/MessagePackDecoder.swift | 42 +++++++++++++++---- Sources/MessagePackEncoder.swift | 23 +++++++--- .../MessagePackType+MapType.swift | 18 ++++++-- Tests/MessagePackerTests/MapPackedTests.swift | 22 ++++++++++ .../MessagePackerTests/MapUnpackedTests.swift | 27 ++++++++++++ 5 files changed, 116 insertions(+), 16 deletions(-) diff --git a/Sources/MessagePackDecoder.swift b/Sources/MessagePackDecoder.swift index d38d4d9..15d320c 100644 --- a/Sources/MessagePackDecoder.swift +++ b/Sources/MessagePackDecoder.swift @@ -9,15 +9,19 @@ import Foundation open class MessagePackDecoder: Decoder { + public var allowIntKeys: Bool public var codingPath: [CodingKey] = [] public var userInfo: [CodingUserInfoKey : Any] = [:] private var storage = MessagePackStorage() - public init() {} + public init(allowIntKeys: Bool = false) { + self.allowIntKeys = allowIntKeys + } - public init(referencing: Data, codingPath: [CodingKey] = []) { + public init(referencing: Data, codingPath: [CodingKey] = [], allowIntKeys: Bool = false) { storage.push(container: referencing) self.codingPath = codingPath + self.allowIntKeys = allowIntKeys } public func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { @@ -123,25 +127,45 @@ private extension MessagePackDecoder { } extension MessagePackDecoder { + private func convert(packKey: MessagePackType.MapType.Key, to: Key.Type) -> Key? { + switch packKey { + case .int(let intValue): + if allowIntKeys { + return Key(intValue: intValue) + } else { + return Key(stringValue: String(intValue)) + } + case .string(let stringValue): + return Key(stringValue: stringValue) + } + } + private func convertToPackKey(from codingKey: CodingKey) -> MessagePackType.MapType.Key { + if allowIntKeys, let intValue = codingKey.intValue { + return .int(intValue) + } else { + return .string(codingKey.stringValue) + } + } + struct KeyedContainer: KeyedDecodingContainerProtocol { private let decoder: MessagePackDecoder private(set) var codingPath: [CodingKey] private(set) var allKeys: [Key] - private var container: [String: Data] = [:] + private var container: [MessagePackType.MapType.Key: Data] = [:] - init(referencing decoder: MessagePackDecoder, container: [String: Data]) { + init(referencing decoder: MessagePackDecoder, container: [MessagePackType.MapType.Key: Data]) { self.decoder = decoder self.codingPath = decoder.codingPath - self.allKeys = container.keys.compactMap { Key(stringValue: $0) } + self.allKeys = container.keys.compactMap { decoder.convert(packKey: $0, to: Key.self) } self.container = container } func contains(_ key: Key) -> Bool { - return container[key.stringValue] != nil + return container[decoder.convertToPackKey(from: key)] != nil } func findEntry(by key: CodingKey) throws -> Data { - guard let entry = self.container[key.stringValue] else { + guard let entry = self.container[decoder.convertToPackKey(from: key)] else { let context = DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(key) (\"\(key.stringValue)\").") throw DecodingError.keyNotFound(key, context) } @@ -542,3 +566,7 @@ extension MessagePackDecoder { } } } + + + + diff --git a/Sources/MessagePackEncoder.swift b/Sources/MessagePackEncoder.swift index 84a5810..c7377de 100644 --- a/Sources/MessagePackEncoder.swift +++ b/Sources/MessagePackEncoder.swift @@ -9,11 +9,14 @@ import Foundation open class MessagePackEncoder: Encoder { + public var allowIntKeys: Bool public var codingPath: [CodingKey] = [] public var userInfo: [CodingUserInfoKey : Any] = [:] fileprivate var storage = MessagePackStorage() - public init() {} + public init(allowIntKeys: Bool = false) { + self.allowIntKeys = allowIntKeys + } public func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { return KeyedEncodingContainer(KeyedContainer(referencing: self, codingPath: codingPath) { [weak self] in @@ -109,8 +112,13 @@ extension MessagePackEncoder { } fileprivate func add(_ value: Data, forKey key: CodingKey) { - let key = encoder.boxMessagePack(key.stringValue) - self.packedData.append(key) + let keyData: Data + if encoder.allowIntKeys, let intValue = key.intValue { + keyData = encoder.boxMessagePack(intValue) + } else { + keyData = encoder.boxMessagePack(key.stringValue) + } + self.packedData.append(keyData) self.packedData.append(value) count += 1 } @@ -420,11 +428,11 @@ extension MessagePackEncoder { } } - class MessagePackReferencingKeyedEncoder: MessagePackEncoder { + class MessagePackReferencingKeyedEncoder: MessagePackEncoder { private let container: KeyedContainer - private let key: CodingKey + private let key: Key2 - init(container: KeyedContainer, key: CodingKey) { + init(container: KeyedContainer, key: Key2) { self.container = container self.key = key super.init() @@ -450,3 +458,6 @@ extension MessagePackEncoder { } } } + + + diff --git a/Sources/MessagePackTypes/MessagePackType+MapType.swift b/Sources/MessagePackTypes/MessagePackType+MapType.swift index 49e946b..fdc1a69 100644 --- a/Sources/MessagePackTypes/MessagePackType+MapType.swift +++ b/Sources/MessagePackTypes/MessagePackType+MapType.swift @@ -102,7 +102,12 @@ extension MessagePackType.MapType { } extension MessagePackType.MapType { - static func split(for value: Data) throws -> [String : Data] { + enum Key: Hashable { + case string(String) + case int(Int) + } + + static func split(for value: Data) throws -> [Key : Data] { guard let firstByte = value.first else { throw MessagePackError.emptyData } let type = try MessagePackType.MapType(firstByte) @@ -110,10 +115,16 @@ extension MessagePackType.MapType { let length = try type.length(value) return try (0..