Thank you for sharing this thoughtful exploration of a new serialization and deserialization model for Swift. I appreciate the detailed breakdown of the limitations of Codable and the rationale behind a new API, particularly the emphasis on performance and flexibility. The visitor pattern is an intriguing approach with clear performance benefits.
I’d like to propose a slightly different, more dynamic API surface that could offer more flexibility while keeping the core serialization logic minimal.
I have concerns about the concrete @JSONCodable macro because it could make it harder for developers to opt into serialization capabilities when introducing new serialization formats. Instead, I believe a more flexible approach — taking inspiration from libraries like the Swift Snapshot Testing framework from Point-Free — would allow serialization strategies to be independently defined and extended by library authors.
@Codable(.json)
struct BlogPost {
let id: Int
let title: String
let subtitle: String?
@CodingKey("date_published")
@CodingFormat(.iso8601)
let publishDate: Date
let body: String
@CodingDefault([])
let tags: [String]
}
In this model, the @Codable macro would generate CodingFields and add conformances (e.g., JSONCodable), while leaving the implementation details open for different serialization strategies. This keeps the standard library API lightweight while allowing developers to introduce optimized encoders or new serialization formats independently.
Handling non-native types
For handling types that are not natively representable in certain formats, I like your approach of using @CodingFormat macros or property wrappers to handle format conversions. Perhaps the compiler could also emit diagnostics to improve developer awareness of tooling.
@Codable(.json)
struct BlogPost {
let publishDate: Date
// Error: Date does not conform to JSONCodable.
// Fix-it: Use a coding format like @CodingFormat(.iso8601) to represent it as a String.
}
Additionally, maybe encoding strategies could even be applied at a higher level:
@Codable(.json)
@CodingKeys(.snakeCased)
@CodingFormat(.defaultDateEncoding(.iso8601))
struct BlogPost {}
Or, alternatively, a more compact form:
@Codable(.json.snakeCasedKeys.defaultDateEncoding(.iso8601))
struct BlogPost {}
This would allow users to specify global encoding preferences directly on the type, similar to how JSONDecoder supports configuration at the instance level today.
Supporting multiple formats
I like @tikitu's idea to handle multiple formats. This model could also take advantage of this approach by stacking macros for multiple encoding formats:
@Codable(.json)
@Codable(.csv)
struct BlogPost { ... }
Slim core API with extensible strategies
A slim core API can live in the standard library with related macros, and CodingStrategies could be separate implementations. This approach would empower developers to implement their own encoders, decoders, and transformation strategies/formatters. A possible standard library surface might include:
macro @Codable<CodingStrategy>
macro @CodingKey
macro @CodingFormat
protocol CodingStrategy {
associatedtype Input
associatedtype Output
}
Maybe some built-in implementations
struct JSONCoder<Input: JSONCodable>: CodingStrategy {
typealias Output = Data
[...]
}
extension CodingStrategy {
static func json<Input>() -> JSONCoder<Input>() where Self == JSONCoder<Input>
}
extension String: JSONCodable {}
extension Int: JSONCodable {}
extension Bool: JSONCodable {}
extension Array: JSONEncodable where Element: JSONEncodable {}
extension CodingFormat: JSONEncodable where ConversionStrategy.Output: JSONEncodable {}
This could then be extended by Foundation adding support for Date and Data:
struct ISO8601Encoder: ConversionStrategy {
typealias Input = Date
typealias Output = String
func encode(_: Input) -> Output { ... }
}
extension Date: PlistCodable {}
This structure would support the most important built-in serialization formats like JSON and Property List while enabling developers to define and implement custom formats such as CSV or MessagePack independently. These are just some initial thoughts — do you think this approach would be feasible?