Skip to content

Commit

Permalink
doc: improved usage documentation (#5)
Browse files Browse the repository at this point in the history
- added minimal usage example in README
- added API availability data
  • Loading branch information
soumyamahunt committed Jun 30, 2023
1 parent 323a94a commit 7086c41
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 28 deletions.
127 changes: 125 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Supercharge `Swift`'s `Codable` implementations with macros.

## Installation

### Swift Package Manager
<details>
<summary><h3>Swift Package Manager</h3></summary>

The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler.

Expand All @@ -50,10 +51,132 @@ Then you can add the `MetaCodable` module product as dependency to the `target`s
```swift
.product(name: "MetaCodable", package: "MetaCodable"),
```
</details>

## Usage

See the full [documentation](https://swiftylab.github.io/MetaCodable/documentation/metacodable/) for API details and articles on sample scenarios.
`MetaCodable` allows to get rid of boiler plate that was often needed in some typical `Codable` implementations with features like:

<details>
<summary>Custom `CodingKey` value declaration per variable, instead of requiring you to write for all fields.</summary>

i.e. in the official [docs](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types#2904057), to define custom `CodingKey` for 2 fields of `Landmark` type you had to write:
```swift
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
var vantagePoints: [Coordinate]

enum CodingKeys: String, CodingKey {
case name = "title"
case foundingYear = "founding_date"
case location
case vantagePoints
}
}
```
But with `MetaCodable` all you have to write is this:
```swift
@Codable
struct Landmark {
@CodablePath("title")
var name: String
@CodablePath("founding_date")
var foundingYear: Int

var location: Coordinate
var vantagePoints: [Coordinate]
}
```
</details>

<details>
<summary>Create flattened model for nested `CodingKey` values.</summary>

i.e. in official [docs](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types#2904058) to decode a JSON like this:
```json
{
"latitude": 0,
"longitude": 0,
"additionalInfo": {
"elevation": 0
}
}
```
You had to write all these boilerplate:
```swift
struct Coordinate {
var latitude: Double
var longitude: Double
var elevation: Double

enum CodingKeys: String, CodingKey {
case latitude
case longitude
case additionalInfo
}

enum AdditionalInfoKeys: String, CodingKey {
case elevation
}
}

extension Coordinate: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
latitude = try values.decode(Double.self, forKey: .latitude)
longitude = try values.decode(Double.self, forKey: .longitude)

let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
}
}

extension Coordinate: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(latitude, forKey: .latitude)
try container.encode(longitude, forKey: .longitude)

var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
try additionalInfo.encode(elevation, forKey: .elevation)
}
}
```
But with `MetaCodable` all you have to write is this:
```swift
@Codable
struct Coordinate {
var latitude: Double
var longitude: Double

@CodablePath("additionalInfo", "elevation")
var elevation: Double
}
```
</details>

<details>
<summary>Provide default value in case of decoding failures and member-wise initializer generated considers these default values.</summary>

Instead of throwing error in case of missing data or type mismatch, you can provide a default value that will be assigned in this case. The memberwise initializer generated also uses this default value for the field. The following definition with `MetaCodable`:
```swift
@Codable
struct CodableData {
@CodablePath(default: "some")
let field: String
}
```
will not throw any error when empty JSON(`{}`) or JSON with type mismatch(`{ "field": 5 }`) is provided. The default value will be assigned in such case. Also, the memberwise initializer generated will look like this:
```swift
init(field: String = "some") {
self.field = field
}
```
</details>

See the full [documentation](https://swiftylab.github.io/MetaCodable/documentation/metacodable/) for API details and advanced use cases.

## Contributing

Expand Down
4 changes: 2 additions & 2 deletions Sources/CodableMacroPlugin/CodableFieldMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import SwiftSyntaxMacros
/// to remove it.
struct CodableFieldMacro: PeerMacro {
/// The name of macro that allows `CodingKey`
/// path customizations
/// path customizations.
static var path: String { "CodablePath" }
/// The name of macro that allows
/// composition of decoding/encoding
/// composition of decoding/encoding.
static var compose: String { "CodableCompose" }

/// Argument label used to provide a default value
Expand Down
1 change: 1 addition & 0 deletions Sources/MetaCodable/Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
/// - Important: The attached declaration must be of a struct type.
@attached(member, names: named(CodingKeys), named(init(from:)), named(encode(to:)), arbitrary)
@attached(conformance)
@available(swift 5.9)
public macro Codable() = #externalMacro(
module: "CodableMacroPlugin",
type: "CodableMacro"
Expand Down
4 changes: 4 additions & 0 deletions Sources/MetaCodable/CodableCompose.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
///
/// - Important: The field type must confirm to `Codable`.
@attached(peer)
@available(swift 5.9)
public macro CodableCompose() = #externalMacro(
module: "CodableMacroPlugin",
type: "CodableFieldMacro"
Expand Down Expand Up @@ -90,6 +91,7 @@ public macro CodableCompose() = #externalMacro(
/// default value type `T` must be the same as
/// field type.
@attached(peer)
@available(swift 5.9)
public macro CodableCompose<T>(
default: T
) = #externalMacro(module: "CodableMacroPlugin", type: "CodableFieldMacro")
Expand All @@ -114,6 +116,7 @@ public macro CodableCompose<T>(
/// - Important: The `helper`'s `T.Coded` associated type
/// must be the same as field type.
@attached(peer)
@available(swift 5.9)
public macro CodableCompose<T: ExternalHelperCoder>(
helper: T
) = #externalMacro(module: "CodableMacroPlugin", type: "CodableFieldMacro")
Expand Down Expand Up @@ -145,6 +148,7 @@ public macro CodableCompose<T: ExternalHelperCoder>(
/// - Important: The `helper`'s `T.Coded` associated type
/// must be the same as field type.
@attached(peer)
@available(swift 5.9)
public macro CodableCompose<T: ExternalHelperCoder>(
default: T.Coded,
helper: T
Expand Down
4 changes: 4 additions & 0 deletions Sources/MetaCodable/CodablePath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
///
/// - Important: The field type must confirm to `Codable`.
@attached(peer)
@available(swift 5.9)
public macro CodablePath(
_ path: StaticString...
) = #externalMacro(
Expand Down Expand Up @@ -77,6 +78,7 @@ public macro CodablePath(
/// default value type `T` must be the same as
/// field type.
@attached(peer)
@available(swift 5.9)
public macro CodablePath<T>(
default: T,
_ path: StaticString...
Expand Down Expand Up @@ -105,6 +107,7 @@ public macro CodablePath<T>(
/// - Important: The `helper`'s `T.Coded` associated type
/// must be the same as field type.
@attached(peer)
@available(swift 5.9)
public macro CodablePath<T: ExternalHelperCoder>(
helper: T,
_ path: StaticString...
Expand Down Expand Up @@ -133,6 +136,7 @@ public macro CodablePath<T: ExternalHelperCoder>(
/// - Important: The `helper`'s `T.Coded` associated type
/// must be the same as field type.
@attached(peer)
@available(swift 5.9)
public macro CodablePath<T: ExternalHelperCoder>(
default: T.Coded,
helper: T,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/// decoding/encoding customizations or to provide decoding/encoding
/// to non-`Codable` types.
///
/// - Tip: Use this type to refactor scenarios where `prpertyWraaper`s
/// - Tip: Use this type to refactor scenarios where `propertyWraaper`s
/// were used to have custom decoding/encoding functionality.
public protocol ExternalHelperCoder {
/// The actual type of value that is going to be decoded/encoded.
Expand Down
45 changes: 22 additions & 23 deletions Sources/MetaCodable/MetaCodable.docc/MetaCodable.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# ``MetaCodable``

@Metadata {
@Available(swift, introduced: "5.9")
}

Supercharge `Swift`'s `Codable` implementations with macros.

## Overview
Expand All @@ -16,31 +20,26 @@ Supercharge `Swift`'s `Codable` implementations with macros.
- Generates member-wise initializer considering the above default value syntax as well.
- Allows to create custom decoding/encoding strategies with ``ExternalHelperCoder``. i.e. ``LossySequenceCoder`` etc.

## Requirements

| Platform | Minimum Swift Version | Installation | Status |
| --- | --- | --- | --- |
| iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ | 5.9 | Swift Package Manager | Fully Tested |
| Linux | 5.9 | Swift Package Manager | Fully Tested |
| Windows | 5.9 | Swift Package Manager | Fully Tested |

## Installation

### Swift Package Manager

The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler.

Once you have your Swift package set up, adding `MetaCodable` as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.

```swift
.package(url: "https://github.com/SwiftyLab/MetaCodable.git", from: "1.0.0"),
```

Then you can add the `MetaCodable` module product as dependency to the `target`s of your choosing, by adding it to the `dependencies` value of your `target`s.

```swift
.product(name: "MetaCodable", package: "MetaCodable"),
```
@TabNavigator {
@Tab("Swift Package Manager") {

The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler.
Once you have your Swift package set up, adding `MetaCodable` as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
```swift
.package(url: "https://github.com/SwiftyLab/MetaCodable.git", from: "1.0.0"),
```
Then you can add the `MetaCodable` module product as dependency to the `target`s of your choosing, by adding it to the `dependencies` value of your `target`s.
```swift
.product(name: "MetaCodable", package: "MetaCodable"),
```
}
}

## Topics

Expand Down

0 comments on commit 7086c41

Please sign in to comment.