-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add OptionalPolymorphicLossyArrayValue property wrapper #21
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
ElonPark
merged 5 commits into
daangn:main
from
KYHyeon:feature/optional-polymorphic-lossy-array
Apr 6, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
1bc4217
feat: add OptionalPolymorphicLossyArrayValue property wrapper
KYHyeon 752049c
feat: add KeyedDecodingContainer extension for OptionalPolymorphicLos…
KYHyeon 90230d1
feat: add OptionalPolymorphicLossyArray typealias to PolymorphicCodab…
KYHyeon f2b0a2b
docs: add OptionalPolymorphicLossyArrayValue to README and CLAUDE.md
KYHyeon f9effd2
fix: handle explicit null in OptionalPolymorphicLossyArrayValue init(…
KYHyeon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
...morphicCodable/Extensions/KeyedDecodingContainer+OptionalPolymorphicLossyArrayValue.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // | ||
| // KeyedDecodingContainer+OptionalPolymorphicLossyArrayValue.swift | ||
| // KarrotCodableKit | ||
| // | ||
| // Created by KYHyeon on 4/6/26. | ||
| // Copyright © 2026 Danggeun Market Inc. All rights reserved. | ||
| // | ||
|
|
||
| import Foundation | ||
|
|
||
| extension KeyedDecodingContainer { | ||
| public func decode<T>( | ||
| _ type: OptionalPolymorphicLossyArrayValue<T>.Type, | ||
| forKey key: Key | ||
| ) throws -> OptionalPolymorphicLossyArrayValue<T> where T: PolymorphicCodableStrategy { | ||
| if let value = try decodeIfPresent(type, forKey: key) { | ||
| return value | ||
| } else { | ||
| return OptionalPolymorphicLossyArrayValue(wrappedValue: nil, outcome: .keyNotFound) | ||
| } | ||
| } | ||
|
|
||
| public func decodeIfPresent<T>( | ||
| _ type: OptionalPolymorphicLossyArrayValue<T>.Type, | ||
| forKey key: Self.Key | ||
| ) throws -> OptionalPolymorphicLossyArrayValue<T>? where T: PolymorphicCodableStrategy { | ||
| // Check if key exists | ||
| guard contains(key) else { | ||
| return nil | ||
| } | ||
|
|
||
| // Check if value is null | ||
| if try decodeNil(forKey: key) { | ||
| return OptionalPolymorphicLossyArrayValue(wrappedValue: nil, outcome: .valueWasNil) | ||
| } | ||
|
|
||
| // Try to decode the array with lossy behavior | ||
| let decoder = try superDecoder(forKey: key) | ||
| return try OptionalPolymorphicLossyArrayValue(from: decoder) | ||
| } | ||
| } |
151 changes: 151 additions & 0 deletions
151
Sources/KarrotCodableKit/PolymorphicCodable/OptionalPolymorphicLossyArrayValue.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| // | ||
| // OptionalPolymorphicLossyArrayValue.swift | ||
| // KarrotCodableKit | ||
| // | ||
| // Created by KYHyeon on 4/6/26. | ||
| // Copyright © 2026 Danggeun Market Inc. All rights reserved. | ||
| // | ||
|
|
||
| import Foundation | ||
|
|
||
| /// A property wrapper that decodes an optional array of polymorphic objects with lossy behavior for individual elements. | ||
| /// | ||
| /// This wrapper combines the optionality handling of ``OptionalPolymorphicArrayValue`` with | ||
| /// the lossy element decoding of ``PolymorphicLossyArrayValue``. | ||
| /// | ||
| /// Key behaviors: | ||
| /// - The array itself is optional (`[Element]?`), returning `nil` when the key is missing or the value is `null` | ||
| /// - Invalid elements within a present array are silently skipped rather than causing decoding failure | ||
| /// | ||
| /// Comparison with similar wrappers: | ||
| /// - ``PolymorphicLossyArrayValue``: For required arrays that default to `[]` when missing or null | ||
| /// - ``OptionalPolymorphicArrayValue``: For optional arrays that throw on invalid elements | ||
| /// - ``DefaultEmptyPolymorphicArrayValue``: For required arrays that default to `[]` when missing or null, strict on elements | ||
| /// | ||
| /// Decoding behavior: | ||
| /// - If the key is missing or the value is `null`, `wrappedValue` is set to `nil` | ||
| /// - If the value is a valid array, each element is decoded using `PolymorphicValue<PolymorphicType>` | ||
| /// - If an element fails to decode, the error is caught and the element is **skipped** | ||
| /// - Empty arrays are decoded as empty arrays, not `nil` | ||
| /// | ||
| /// Encoding behavior: | ||
| /// - If `wrappedValue` is `nil`, encodes as `null` | ||
| /// - If `wrappedValue` contains an array, each element is encoded using the `PolymorphicType` strategy | ||
| /// | ||
| @propertyWrapper | ||
| public struct OptionalPolymorphicLossyArrayValue<PolymorphicType: PolymorphicCodableStrategy> { | ||
| /// The decoded optional array containing only the successfully decoded polymorphic elements. | ||
| /// `nil` if the key is missing or the value is `null`. | ||
| public var wrappedValue: [PolymorphicType.ExpectedType]? | ||
|
|
||
| /// Tracks the outcome of the decoding process for resilient decoding | ||
| public let outcome: ResilientDecodingOutcome | ||
|
|
||
| #if DEBUG | ||
| /// Results of decoding each element in the array (DEBUG only) | ||
| let results: [Result<PolymorphicType.ExpectedType, Error>] | ||
| #endif | ||
|
|
||
| public init(wrappedValue: [PolymorphicType.ExpectedType]?) { | ||
| self.wrappedValue = wrappedValue | ||
| outcome = .decodedSuccessfully | ||
| #if DEBUG | ||
| results = [] | ||
| #endif | ||
| } | ||
|
|
||
| #if DEBUG | ||
| init( | ||
| wrappedValue: [PolymorphicType.ExpectedType]?, | ||
| outcome: ResilientDecodingOutcome, | ||
| results: [Result<PolymorphicType.ExpectedType, Error>] = [] | ||
| ) { | ||
| self.wrappedValue = wrappedValue | ||
| self.outcome = outcome | ||
| self.results = results | ||
| } | ||
| #else | ||
| init(wrappedValue: [PolymorphicType.ExpectedType]?, outcome: ResilientDecodingOutcome) { | ||
| self.wrappedValue = wrappedValue | ||
| self.outcome = outcome | ||
| } | ||
| #endif | ||
|
|
||
| #if DEBUG | ||
| /// The projected value providing access to decoding outcome | ||
| public var projectedValue: PolymorphicLossyArrayProjectedValue<PolymorphicType.ExpectedType> { | ||
| PolymorphicLossyArrayProjectedValue(outcome: outcome, results: results) | ||
| } | ||
| #endif | ||
| } | ||
|
|
||
| extension OptionalPolymorphicLossyArrayValue: Decodable { | ||
| private struct AnyDecodableValue: Decodable {} | ||
|
|
||
| public init(from decoder: Decoder) throws { | ||
| // First check if the value is nil | ||
| let singleValueContainer = try decoder.singleValueContainer() | ||
| if singleValueContainer.decodeNil() { | ||
| self.init(wrappedValue: nil, outcome: .valueWasNil) | ||
| return | ||
| } | ||
|
|
||
| // Decode as an array with lossy behavior | ||
| var container = try decoder.unkeyedContainer() | ||
|
|
||
| var elements = [PolymorphicType.ExpectedType]() | ||
| #if DEBUG | ||
| var results = [Result<PolymorphicType.ExpectedType, Error>]() | ||
| #endif | ||
|
|
||
| while !container.isAtEnd { | ||
| do { | ||
| let value = try container.decode(PolymorphicValue<PolymorphicType>.self).wrappedValue | ||
| elements.append(value) | ||
| #if DEBUG | ||
| results.append(.success(value)) | ||
| #endif | ||
| } catch { | ||
| // Decoding processing to prevent infinite loops if decoding fails. | ||
| _ = try? container.decode(AnyDecodableValue.self) | ||
| #if DEBUG | ||
| results.append(.failure(error)) | ||
| #endif | ||
| } | ||
| } | ||
|
|
||
| #if DEBUG | ||
| self.init(wrappedValue: elements, outcome: .decodedSuccessfully, results: results) | ||
| #else | ||
| self.init(wrappedValue: elements, outcome: .decodedSuccessfully) | ||
| #endif | ||
| } | ||
| } | ||
|
|
||
| extension OptionalPolymorphicLossyArrayValue: Encodable { | ||
| public func encode(to encoder: Encoder) throws { | ||
| if let array = wrappedValue { | ||
| let polymorphicValues = array.map { | ||
| PolymorphicValue<PolymorphicType>(wrappedValue: $0) | ||
| } | ||
| try polymorphicValues.encode(to: encoder) | ||
| } else { | ||
| var container = encoder.singleValueContainer() | ||
| try container.encodeNil() | ||
| } | ||
KYHyeon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| extension OptionalPolymorphicLossyArrayValue: Equatable where PolymorphicType.ExpectedType: Equatable { | ||
| public static func == (lhs: Self, rhs: Self) -> Bool { | ||
| lhs.wrappedValue == rhs.wrappedValue | ||
| } | ||
| } | ||
|
|
||
| extension OptionalPolymorphicLossyArrayValue: Hashable where PolymorphicType.ExpectedType: Hashable { | ||
| public func hash(into hasher: inout Hasher) { | ||
| hasher.combine(wrappedValue) | ||
| } | ||
| } | ||
|
|
||
| extension OptionalPolymorphicLossyArrayValue: Sendable where PolymorphicType.ExpectedType: Sendable {} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.