From 85bd2d7ed9f36c9b22583aab9007ccfe4f80f8b5 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 30 Aug 2022 20:28:01 +0100 Subject: [PATCH] Implement custom JSONEncoder and JSONDecoder --- Sources/XXClient/Helpers/JSONDecoder.swift | 64 ++++++++++++++ Sources/XXClient/Helpers/JSONEncoder.swift | 52 ++++++++++++ .../Helpers/JSONDecoderTests.swift | 84 +++++++++++++++++++ .../Helpers/JSONEncoderTests.swift | 84 +++++++++++++++++++ 4 files changed, 284 insertions(+) create mode 100644 Sources/XXClient/Helpers/JSONDecoder.swift create mode 100644 Sources/XXClient/Helpers/JSONEncoder.swift create mode 100644 Tests/XXClientTests/Helpers/JSONDecoderTests.swift create mode 100644 Tests/XXClientTests/Helpers/JSONEncoderTests.swift diff --git a/Sources/XXClient/Helpers/JSONDecoder.swift b/Sources/XXClient/Helpers/JSONDecoder.swift new file mode 100644 index 00000000..fc92d05f --- /dev/null +++ b/Sources/XXClient/Helpers/JSONDecoder.swift @@ -0,0 +1,64 @@ +import CustomDump +import Foundation + +public class JSONDecoder: Foundation.JSONDecoder { + public override init() { + super.init() + } + + public override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable { + do { + let data = convertNumberToString(in: data, at: "Value") + return try super.decode(type, from: data) + } catch { + throw JSONDecodingError(error, data: data) + } + } + + func convertNumberToString( + in input: Data, + at key: String + ) -> Data { + guard var string = String(data: input, encoding: .utf8) else { + return input + } + string = string.replacingOccurrences( + of: #""\#(key)"( *):( *)([0-9]+)( *)(,*)"#, + with: #""\#(key)"$1:$2"$3"$4$5"#, + options: [.regularExpression] + ) + guard let output = string.data(using: .utf8) else { + return input + } + return output + } +} + +public struct JSONDecodingError: Error, CustomStringConvertible, CustomDumpReflectable { + public init(_ underlayingError: Error, data: Data) { + self.underlayingError = underlayingError + self.data = data + self.string = String(data: data, encoding: .utf8) + } + + public var underlayingError: Error + public var data: Data + public var string: String? + + public var description: String { + var description = "" + customDump(self, to: &description) + return description + } + + public var customDumpMirror: Mirror { + Mirror( + self, + children: [ + "underlayingError": underlayingError, + "data": String(data: data, encoding: .utf8) ?? data + ], + displayStyle: .struct + ) + } +} diff --git a/Sources/XXClient/Helpers/JSONEncoder.swift b/Sources/XXClient/Helpers/JSONEncoder.swift new file mode 100644 index 00000000..ba9c2a31 --- /dev/null +++ b/Sources/XXClient/Helpers/JSONEncoder.swift @@ -0,0 +1,52 @@ +import CustomDump +import Foundation + +public class JSONEncoder: Foundation.JSONEncoder { + public override init() { + super.init() + } + + public override func encode<T>(_ value: T) throws -> Data where T: Encodable { + do { + var data = try super.encode(value) + data = convertStringToNumber(in: data, at: "Value") + return data + } catch { + throw JSONEncodingError(error, value: value) + } + } + + func convertStringToNumber( + in input: Data, + at key: String + ) -> Data { + guard var string = String(data: input, encoding: .utf8) else { + return input + } + string = string.replacingOccurrences( + of: #""\#(key)"( *):( *)"([0-9]+)"( *)(,*)"#, + with: #""\#(key)"$1:$2$3$4$5"#, + options: [.regularExpression] + ) + guard let output = string.data(using: .utf8) else { + return input + } + return output + } +} + +public struct JSONEncodingError: Error, CustomStringConvertible { + public init(_ underlayingError: Error, value: Any) { + self.underlayingError = underlayingError + self.value = value + } + + public var underlayingError: Error + public var value: Any + + public var description: String { + var description = "" + customDump(self, to: &description) + return description + } +} diff --git a/Tests/XXClientTests/Helpers/JSONDecoderTests.swift b/Tests/XXClientTests/Helpers/JSONDecoderTests.swift new file mode 100644 index 00000000..16f2afcb --- /dev/null +++ b/Tests/XXClientTests/Helpers/JSONDecoderTests.swift @@ -0,0 +1,84 @@ +import CustomDump +import XCTest +@testable import XXClient + +final class JSONDecoderTests: XCTestCase { + func testConvertingNumberToString() { + assertConvertingNumberToString( + input: #"{"number":1234567890,"text":"hello"}"#, + key: "number", + expectedOutput: #"{"number":"1234567890","text":"hello"}"# + ) + + assertConvertingNumberToString( + input: #"{"text":"hello","number":1234567890}"#, + key: "number", + expectedOutput: #"{"text":"hello","number":"1234567890"}"# + ) + + assertConvertingNumberToString( + input: #"{ "number" : 1234567890 , "text" : "hello" }"#, + key: "number", + expectedOutput: #"{ "number" : "1234567890" , "text" : "hello" }"# + ) + + assertConvertingNumberToString( + input: #"{ "text" : "hello" , "number" : 1234567890 }"#, + key: "number", + expectedOutput: #"{ "text" : "hello" , "number" : "1234567890" }"# + ) + + assertConvertingNumberToString( + input: """ + { + "number": 1234567890, + "text": "hello" + } + """, + key: "number", + expectedOutput: """ + { + "number": "1234567890", + "text": "hello" + } + """ + ) + + assertConvertingNumberToString( + input: """ + { + "text": "hello", + "number": 1234567890 + } + """, + key: "number", + expectedOutput: """ + { + "text": "hello", + "number": "1234567890" + } + """ + ) + } +} + +private func assertConvertingNumberToString( + input: String, + key: String, + expectedOutput: String, + file: StaticString = #file, + line: UInt = #line +) { + XCTAssertNoDifference( + String( + data: JSONDecoder().convertNumberToString( + in: input.data(using: .utf8)!, + at: key + ), + encoding: .utf8 + )!, + expectedOutput, + file: file, + line: line + ) +} diff --git a/Tests/XXClientTests/Helpers/JSONEncoderTests.swift b/Tests/XXClientTests/Helpers/JSONEncoderTests.swift new file mode 100644 index 00000000..58fbfae7 --- /dev/null +++ b/Tests/XXClientTests/Helpers/JSONEncoderTests.swift @@ -0,0 +1,84 @@ +import CustomDump +import XCTest +@testable import XXClient + +final class JSONEncoderTests: XCTestCase { + func testConvertingStringToNumber() { + assertConvertingStringToNumber( + input: #"{"number":"1234567890","text":"hello"}"#, + key: "number", + expectedOutput: #"{"number":1234567890,"text":"hello"}"# + ) + + assertConvertingStringToNumber( + input: #"{"text":"hello","number":"1234567890"}"#, + key: "number", + expectedOutput: #"{"text":"hello","number":1234567890}"# + ) + + assertConvertingStringToNumber( + input: #"{ "number" : "1234567890" , "text" : "hello" }"#, + key: "number", + expectedOutput: #"{ "number" : 1234567890 , "text" : "hello" }"# + ) + + assertConvertingStringToNumber( + input: #"{ "text" : "hello" , "number" : "1234567890" }"#, + key: "number", + expectedOutput: #"{ "text" : "hello" , "number" : 1234567890 }"# + ) + + assertConvertingStringToNumber( + input: """ + { + "number": "1234567890", + "text": "hello" + } + """, + key: "number", + expectedOutput: """ + { + "number": 1234567890, + "text": "hello" + } + """ + ) + + assertConvertingStringToNumber( + input: """ + { + "text": "hello", + "number": "1234567890" + } + """, + key: "number", + expectedOutput: """ + { + "text": "hello", + "number": 1234567890 + } + """ + ) + } +} + +private func assertConvertingStringToNumber( + input: String, + key: String, + expectedOutput: String, + file: StaticString = #file, + line: UInt = #line +) { + XCTAssertNoDifference( + String( + data: JSONEncoder().convertStringToNumber( + in: input.data(using: .utf8)!, + at: key + ), + encoding: .utf8 + )!, + expectedOutput, + file: file, + line: line + ) +} -- GitLab