Skip to content

NewCodable macros#1808

Merged
kperryua merged 15 commits intoswiftlang:experimental/new-codablefrom
tevelee:experimental/new-codable
Mar 11, 2026
Merged

NewCodable macros#1808
kperryua merged 15 commits intoswiftlang:experimental/new-codablefrom
tevelee:experimental/new-codable

Conversation

@tevelee
Copy link
Copy Markdown

@tevelee tevelee commented Mar 10, 2026

Added Swift macros for complementing the NewCodable effort

Motivation:

Several of the existing tests had commented-out @JSONCodable annotations sketching out what the macro-driven API should eventually look like. This PR picks up that thread and implements the macros.

Modifications:

  • Added a NewCodableMacros compiler plugin target with the following macros:
    • @JSONEncodable: generates the CodingFields enum and JSONEncodable conformance
    • @JSONDecodable: generates JSONDecodable conformance with string-based key matching via decodeEachKeyAndValue
    • @JSONCodable: combines both, delegating to the individual macro implementations
    • @CodingKey("custom_name"): overrides the JSON key for a property
    • @CodableDefault(value): provides a fallback when a key is missing during decoding
    • @CodableAlias("alt1", "alt2"): accepts alternative key names during decoding
    • The annotation macros are peer macros that produce no output themselves, they're read as markers by the encoder/decoder macros at expansion time.
    • Replaced the hand-written conformances in CodableRevolutionTests with macro-annotated structs, cutting ~170 lines of manual boilerplate.

Result:

Structs can now be annotated with @JSONCodable to get the same optimized encode/decode code without writing it by hand.

Testing:

  • Added NewCodableMacrosTests with expansion tests covering basic structs, empty structs, optionals, @CodingKey, @CodableDefault, @CodableAlias, error diagnostics, and the combined @JSONCodable macro.
  • Updated existing CodableRevolutionTests to exercise the macros end-to-end against real encoding/decoding.

@codingkey

This comment was marked as off-topic.

@tevelee tevelee marked this pull request as ready for review March 10, 2026 21:38
Comment thread Sources/NewCodable/JSON/JSONParserDecoder.swift Outdated
Comment thread Sources/NewCodableMacros/JSONDecodableMacro.swift Outdated
Comment thread Sources/NewCodable/JSON/JSONParserDecoder.swift Outdated
Comment thread Sources/NewCodable/Macros.swift Outdated
Comment thread Sources/NewCodableMacros/JSONEncodableMacro.swift Outdated
Comment thread Sources/NewCodableMacros/JSONEncodableMacro.swift Outdated
Comment thread Tests/NewCodableMacrosTests/JSONEncodableMacroTests.swift Outdated
Comment thread Sources/NewCodableMacros/JSONEncodableMacro.swift Outdated
@kperryua kperryua added the new-codable Related to new Swift (de)serialization APIs label Mar 10, 2026
@tevelee tevelee force-pushed the experimental/new-codable branch 3 times, most recently from d6b408a to 5d52714 Compare March 11, 2026 11:01
@mateusrodriguesxyz
Copy link
Copy Markdown
Contributor

Maybe @DecodableAlias would be more accurate than @CodableAlias.

@kperryua
Copy link
Copy Markdown
Contributor

Maybe @DecodableAlias would be more accurate than @CodableAlias.

I support that.

I haven't checked whether it is or not, but we also need to make sure that this is compatible with @CodingKey.

@tevelee
Copy link
Copy Markdown
Author

tevelee commented Mar 11, 2026

Good point, updated it to @DecodableAlias. It composes with @CodingKey as expected: the @CodingKey value remains the canonical serialized key, and @DecodableAlias adds additional accepted decode keys.
I also added/updated macro tests for both @JSONDecodable and @JSONCodable covering @CodingKey("user_name") + @DecodableAlias("username").

Comment thread Sources/NewCodableMacros/JSONDecodableMacro.swift
Comment thread Tests/NewCodableMacrosTests/JSONDecodableMacroTests.swift
Comment thread Tests/NewCodableTests/CodableRevolutionTests.swift
Comment thread Tests/NewCodableMacrosTests/JSONCodableMacroTests.swift Outdated
@mateusrodriguesxyz
Copy link
Copy Markdown
Contributor

Could @DecodableAlias support nesting?

{
  "latitude": 37.332,
  "longitude": -122.011,
  "additionalInfo": {
    "elevation": 30.5
  }
}
@JSONCodable
struct Coordinate {
    var latitude: Double
    var longitude: Double
    @DecodableAlias("additionalInfo.elevation") var elevation: Double
}

@tevelee
Copy link
Copy Markdown
Author

tevelee commented Mar 11, 2026

Could @DecodableAlias support nesting?

Eventually yes, but I haven’t identified an obvious quick win to tackle it yet.

@tevelee tevelee force-pushed the experimental/new-codable branch from e93dd0d to 76e0a9e Compare March 11, 2026 18:55
@mateusrodriguesxyz
Copy link
Copy Markdown
Contributor

Could @DecodableAlias support nesting?

Eventually yes, but I haven’t identified an obvious quick win to tackle it yet.

I've brought this up because it's an example from the documentation of having to resort to manually implementing a decoder init just to get rid of a simple nesting, which can be a pain.

@kperryua
Copy link
Copy Markdown
Contributor

Agreed that is should be possible. Are you aware of any prior art for this kind of annotation in other macro-based systems? I don't see one in Serde's suite of macros. It could make the implementation rather tricky, introducing more complex cases. The "easier" way out would be collecting parsed JSONPrimitives and then decoding all these values from them after the fact, but it's generally better to avoid that if possible.

One thing to keep in mind is that we should seek maximal compatibility with the CommonCodable protocols and forthcoming macro. Not every serialization format is able to provide a "primitive" type (it might not self-describing for instance), so the most macro functionality we can provide without relying on that, the better.

@kperryua
Copy link
Copy Markdown
Contributor

Looks good! The failures are the same ones we started with (that I still need to address). Merging. Thanks again!

@kperryua kperryua merged commit 8557f53 into swiftlang:experimental/new-codable Mar 11, 2026
17 of 19 checks passed
@mateusrodriguesxyz
Copy link
Copy Markdown
Contributor

Are you aware of any prior art for this kind of annotation in other macro-based systems? I don't see one in Serde's suite of macros.

The closest official thing I have found, but not macro related, is pydantic AliasPath.

from pydantic import BaseModel, Field, AliasPath

class Coordinate(BaseModel):
    latitude: float
    longitude: float
    elevation: float = Field(validation_alias=AliasPath("additionalInfo", "elevation"))


json_data = """
{
  "latitude": 37.332,
  "longitude": -122.011,
  "additionalInfo": {
    "elevation": 30.5
  }
}
"""

coordinate = Coordinate.model_validate_json(json_data)

Serde can custom deserialize a field:

#[derive(Debug, Deserialize)]
struct Coordinate {
    latitude: f64,
    longitude: f64,

    #[serde(rename = "additionalInfo", deserialize_with = "deserialize_elevation")]
    elevation: f64,
}

fn deserialize_elevation<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
    D: Deserializer<'de>,
{
    let value: Value = Deserialize::deserialize(deserializer)?;
    Ok(value["elevation"].as_f64().unwrap())
}

fn main() {
    let json_data = r#"
    {
      "latitude": 37.332,
      "longitude": -122.011,
      "additionalInfo": {
        "elevation": 30.5
      }
    }
    "#;

    let coordinate: Coordinate = serde_json::from_str(json_data).unwrap();
}

There's also this crate:
https://crates.io/crates/serde_flat_path
https://stackoverflow.com/a/75991416

#[flat_path]
#[derive(Deserialize)]
struct Foo {
    title: String,
    foo: bool,
    #[flat_path("config.bar.name")]
    bar_name: String,
}

t089 pushed a commit to t089/swift-foundation that referenced this pull request Mar 21, 2026
* fix build issues

* @JSONEncodable macro

* @codingkey macro

* @JSONDecodable macro

* @JSONCodable macro

* @CodableDefault macro

* @CodableAlias macro

* Code review: docs

* Code review: include missing field names

* Code review: CodingFields enum

* Code review: separate CodingKeys conformances

* Code review: permissive handling of unknown keys

* Code review: rename alias macro

* Code review: #expect(throws:)

* Code review: use decodeEachField
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-codable Related to new Swift (de)serialization APIs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants