Skip to content

Fix user-specified Date objects not being output in ISO8601 format #273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/Segment/ObjC/ObjCAnalytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ extension ObjCAnalytics {
var result: [String: Any]? = nil
if let system: System = analytics.store.currentState() {
do {
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
let json = try encoder.encode(system.settings)
if let r = try JSONSerialization.jsonObject(with: json) as? [String: Any] {
result = r
Expand Down
4 changes: 2 additions & 2 deletions Sources/Segment/ObjC/ObjCConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public class ObjCConfiguration: NSObject {
get {
var result = [String: Any]()
do {
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
let json = try encoder.encode(configuration.values.defaultSettings)
if let r = try JSONSerialization.jsonObject(with: json) as? [String: Any] {
result = r
Expand All @@ -89,7 +89,7 @@ public class ObjCConfiguration: NSObject {
set(value) {
do {
let json = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
let decoder = JSONDecoder()
let decoder = JSONDecoder.default
let settings = try decoder.decode(Settings.self, from: json)
configuration.defaultSettings(settings)
} catch {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Segment/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public struct Settings: Codable {
static public func load(from url: URL?) -> Settings? {
guard let url = url else { return nil }
guard let data = try? Data(contentsOf: url) else { return nil }
let settings = try? JSONDecoder().decode(Settings.self, from: data)
let settings = try? JSONDecoder.default.decode(Settings.self, from: data)
return settings
}

Expand Down Expand Up @@ -80,7 +80,7 @@ public struct Settings: Codable {
var result: T? = nil
guard let settings = integrations?.dictionaryValue else { return nil }
if let dict = settings[key], let jsonData = try? JSONSerialization.data(withJSONObject: dict) {
result = try? JSONDecoder().decode(T.self, from: jsonData)
result = try? JSONDecoder.default.decode(T.self, from: jsonData)
}
return result
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Segment/Utilities/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public class HTTPClient {
}

do {
let responseJSON = try JSONDecoder().decode(Settings.self, from: data)
let responseJSON = try JSONDecoder.default.decode(Settings.self, from: data)
completion(true, responseJSON)
} catch {
self?.analytics?.reportInternalError(AnalyticsError.jsonUnableToDeserialize(error))
Expand Down
14 changes: 10 additions & 4 deletions Sources/Segment/Utilities/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public enum JSON: Equatable {

// For Value types
public init<T: Codable>(with value: T) throws {
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
let json = try encoder.encode(value)
let output = try JSONSerialization.jsonObject(with: json)
try self.init(output)
Expand All @@ -58,6 +58,8 @@ public enum JSON: Equatable {
// handle swift types
case Optional<Any>.none:
self = .null
case let date as Date:
self = .string(date.iso8601())
case let url as URL:
self = .string(url.absoluteString)
case let string as String:
Expand Down Expand Up @@ -134,7 +136,7 @@ extension Encodable {
public func toString(pretty: Bool) -> String {
var returnString = ""
do {
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
if pretty {
encoder.outputFormatting = .prettyPrinted
}
Expand Down Expand Up @@ -182,7 +184,11 @@ extension JSON {
public func codableValue<T: Codable>() -> T? {
var result: T? = nil
if let dict = dictionaryValue, let jsonData = try? JSONSerialization.data(withJSONObject: dict) {
result = try? JSONDecoder().decode(T.self, from: jsonData)
do {
result = try JSONDecoder.default.decode(T.self, from: jsonData)
} catch {
print(error)
}
}
return result
}
Expand Down Expand Up @@ -402,7 +408,7 @@ extension JSON {
if let v = value as? [String: Any] {
if let jsonData = try? JSONSerialization.data(withJSONObject: v) {
do {
result = try JSONDecoder().decode(T.self, from: jsonData)
result = try JSONDecoder.default.decode(T.self, from: jsonData)
} catch {
Analytics.segmentLog(message: "Unable to decode object (\(keyPath)) to a Codable: \(error)", kind: .error)
}
Expand Down
28 changes: 27 additions & 1 deletion Sources/Segment/Utilities/iso8601.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import Foundation

enum SegmentISO8601DateFormatter {

static let shared: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions.update(with: .withFractionalSeconds)
Expand All @@ -28,3 +27,30 @@ internal extension String {
return SegmentISO8601DateFormatter.shared.date(from: self)
}
}

extension DateFormatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
}

extension JSONDecoder {
static var `default`: JSONDecoder {
let d = JSONDecoder()
d.dateDecodingStrategy = .formatted(DateFormatter.iso8601)
return d
}
}

extension JSONEncoder {
static var `default`: JSONEncoder {
let e = JSONEncoder()
e.dateEncodingStrategy = .formatted(DateFormatter.iso8601)
return e
}
}
6 changes: 3 additions & 3 deletions Tests/Segment-Tests/Analytics_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ final class Analytics_Tests: XCTestCase {

}

func testAsyncOperatingMode() {
func testAsyncOperatingMode() throws {
// Use a specific writekey to this test so we do not collide with other cached items.
let analytics = Analytics(configuration: Configuration(writeKey: "testFlush_asyncMode")
.flushInterval(9999)
Expand Down Expand Up @@ -721,7 +721,7 @@ final class Analytics_Tests: XCTestCase {
XCTAssertEqual(analytics.pendingUploads!.count, 0)
}

func testSyncOperatingMode() {
func testSyncOperatingMode() throws {
// Use a specific writekey to this test so we do not collide with other cached items.
let analytics = Analytics(configuration: Configuration(writeKey: "testFlush_syncMode")
.flushInterval(9999)
Expand Down Expand Up @@ -755,7 +755,7 @@ final class Analytics_Tests: XCTestCase {
XCTAssertEqual(analytics.pendingUploads!.count, 0)
}

func testFindAll() {
func testFindAll() throws {
let analytics = Analytics(configuration: Configuration(writeKey: "testFindAll")
.flushInterval(9999)
.flushAt(9999)
Expand Down
36 changes: 32 additions & 4 deletions Tests/Segment-Tests/JSON_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class JSONTests: XCTestCase {
let traits = try? JSON(["email": "[email protected]"])
let userInfo = UserInfo(anonymousId: "1234", userId: "brandon", traits: traits, referrer: nil)

let encoder = JSONEncoder()
let encoder = JSONEncoder.default
encoder.outputFormatting = .prettyPrinted

do {
Expand All @@ -51,6 +51,34 @@ class JSONTests: XCTestCase {
}
}

func testJSONDateHandling() throws {
struct TestStruct: Codable {
let myDate: Date
}

let now = Date(timeIntervalSinceNow: 0)

let test = TestStruct(myDate: now)
let object = try JSON(with: test)
let encoder = JSONEncoder.default
encoder.outputFormatting = .prettyPrinted

do {
let json = try encoder.encode(object)
XCTAssertNotNil(json)
let newTest = try! JSONDecoder.default.decode(TestStruct.self, from: json)
XCTAssertEqual(newTest.myDate.toString(), now.toString())
} catch {
print(error)
XCTFail()
}

let dummyProps = ["myDate": now] // <- conforms to Codable
let j = try! JSON(dummyProps)
let anotherTest: TestStruct! = j.codableValue()
XCTAssertEqual(anotherTest.myDate.toString(), now.toString())
}

func testJSONCollectionTypes() throws {
let testSet: Set = ["1", "2", "3"]
let traits = try! JSON(["type": NSNull(), "preferences": ["bwack"], "key": testSet])
Expand All @@ -63,13 +91,13 @@ class JSONTests: XCTestCase {

func testJSONNil() throws {
let traits = try JSON(["type": NSNull(), "preferences": ["bwack"], "key": nil] as [String : Any?])
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
encoder.outputFormatting = .prettyPrinted

do {
let json = try encoder.encode(traits)
XCTAssertNotNil(json)
let decoded = try JSONDecoder().decode(Personal.self, from: json)
let decoded = try JSONDecoder.default.decode(Personal.self, from: json)
XCTAssertNil(decoded.type, "Type should be nil")
}
}
Expand All @@ -81,7 +109,7 @@ class JSONTests: XCTestCase {

let test = TestStruct(blah: "hello")
let object = try JSON(with: test)
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
encoder.outputFormatting = .prettyPrinted

do {
Expand Down