From 0b2598f666847306d50d8a03f63cdce8eda281d7 Mon Sep 17 00:00:00 2001 From: Alexander Loren Date: Mon, 19 Oct 2026 13:54:34 -0700 Subject: [PATCH] PascalCase decoding strategy --- Sources/JellyKit/Client.swift | 7 +++- .../PascalCaseKey+KeyDecodingStrategy.swift | 39 +++++++++++++++++++ Sources/JellyKit/Models/User.swift | 8 ---- 3 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 Sources/JellyKit/Extensions/PascalCaseKey+KeyDecodingStrategy.swift diff --git a/Sources/JellyKit/Client.swift b/Sources/JellyKit/Client.swift index 6b0ed76..4f56d68 100644 --- a/Sources/JellyKit/Client.swift +++ b/Sources/JellyKit/Client.swift @@ -7,6 +7,9 @@ import Foundation +/// Every `Request` must pass through this `Client`. +/// Manages merging `Request` and default headers as well as creating the `URLRequest`. +/// Provides its own `JSONDecoder` and `JSONEncoder` with a PascalCase `CodingKey` strategy. public struct Client { // MARK: - Properties private let server: String @@ -20,6 +23,8 @@ public struct Client { self.server = server self.session = session self.defaultHeaders = headers + + decoder.keyDecodingStrategy = .convertFromPascalCase } /// When all a client needs is the HTTP status code of an endpoint, this function should be used. @@ -43,7 +48,7 @@ public struct Client { func dataRequest(_ request: any Request) async throws -> T? where T: Codable { let urlRequest = try createRequestObject(for: request) let (data, response) = try await session.data(for: urlRequest) - guard let response = response as? HTTPURLResponse else { + guard response is HTTPURLResponse else { throw RequestError.invalidResponse } diff --git a/Sources/JellyKit/Extensions/PascalCaseKey+KeyDecodingStrategy.swift b/Sources/JellyKit/Extensions/PascalCaseKey+KeyDecodingStrategy.swift new file mode 100644 index 0000000..a79de28 --- /dev/null +++ b/Sources/JellyKit/Extensions/PascalCaseKey+KeyDecodingStrategy.swift @@ -0,0 +1,39 @@ +// +// PascalCaseKey+KeyDecodingStrategy.swift +// JellyKit +// +// Created by Alex Loren on 10/19/26. +// + +import Foundation + +extension JSONDecoder.KeyDecodingStrategy { + // MARK: - Computed Properties + + /// A key decoding strategy that converts pascal-case keys to camel-case keys. + static var convertFromPascalCase: Self { + .custom { codingPath in + let key = codingPath.last!.stringValue + let converted = key.prefix(1).lowercased() + key.dropFirst() + return PascalCaseKey(stringValue: converted) + } + } +} + +/// Simply a reusable `CodingKey` for converting PascalCase to camelCase. +private struct PascalCaseKey: CodingKey { + // MARK: - Properties + let stringValue: String + let intValue: Int? + + // MARK: - Functions + init(stringValue: String) { + self.stringValue = stringValue + intValue = nil + } + + init?(intValue: Int) { + stringValue = String(intValue) + self.intValue = intValue + } +} diff --git a/Sources/JellyKit/Models/User.swift b/Sources/JellyKit/Models/User.swift index b8ee2e4..4ee6b95 100644 --- a/Sources/JellyKit/Models/User.swift +++ b/Sources/JellyKit/Models/User.swift @@ -25,12 +25,4 @@ struct User: Codable { /// If not `nil` a request can be made to retreive the profile image for the `User`. var primaryImageTag: String? - - enum CodingKeys: String, CodingKey { - case id = "Id" - case serverId = "ServerId" - case name = "Name" - case hasPassword = "HasPassword" - case primaryImageTag = "PrimaryImageTag" - } }