Skip to content
Merged
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# EditorConfig is awesome: https://editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.swift]
indent_style = space
indent_size = 4

[*.md]
indent_style = space
indent_size = 4
70 changes: 57 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,63 @@
# This workflow will build a Swift project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift

name: ReerCodable
name: CI

on:
push:
branches: [ "main" ]
branches:
- main
pull_request:
branches: [ "main" ]
branches:
- "**"
schedule:
- cron: "3 3 * * 2" # 3:03 AM, every Tuesday

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
Linux:
runs-on: [ubuntu-latest]
container: swift:5.10
jobs:
macOS:
if: github.event_name != 'pull_request' || !contains(github.event.pull_request.title, '[skip ci]')
name: macOS (Swift ${{ matrix.swift }})
runs-on: macos-15
strategy:
fail-fast: false
matrix:
swift:
- "6.2"
steps:
- uses: actions/checkout@v1
- name: Linux
run: swift build -v
- name: Git Checkout
uses: actions/checkout@v5

- name: Disable SwiftSyntax Prebuilts
run: |
defaults write com.apple.dt.Xcode IDEPackageEnablePrebuilts -bool NO

# nuke cache to ensure it takes effect
rm -rf ~/Library/Developer/Xcode/DerivedData
rm -rf ~/Library/Developer/Xcode/SourcePackages
rm -rf ~/Library/Caches/org.swift.swiftpm

- name: Run Tests
uses: mxcl/xcodebuild@v3
with:
platform: macOS
swift: ~${{ matrix.swift }}
action: test
verbosity: xcbeautify
linux:
if: github.event_name != 'pull_request' || !contains(github.event.pull_request.title, '[skip ci]')
name: Linux (Swift ${{ matrix.swift }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
swift:
- "6.2"
container:
image: swift:${{ matrix.swift }}
steps:
- name: Git Checkout
uses: actions/checkout@v5

- name: Run Tests
run: swift test
38 changes: 37 additions & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.10
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -22,7 +22,8 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "601.0.1"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.0"),
.package(url: "https://github.com/swiftlang/swift-syntax", "600.0.0"..<"603.0.0"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
Expand All @@ -45,8 +46,9 @@ let package = Package(
dependencies: [
"ReerCodable",
"ReerCodableMacros",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
.product(name: "MacroTesting", package: "swift-macro-testing"),
]
),
]
],
swiftLanguageModes: [.v5],
)
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,30 @@ enum Gender: String {
}
```

For explicit control you can annotate properties with `@DecodingDefault`, `@EncodingDefault`, or `@CodingDefault`:

```swift
@Decodable
struct Flags {
@DecodingDefault(false)
var isEnabled: Bool
}

@Encodable
struct Payload {
@EncodingDefault("anonymous")
var nickname: String?
}

@Codable
struct Preferences {
@CodingDefault([String]())
var tags: [String]?
}
```

`@DecodingDefault` supplies a fallback when decoding throws or keys are missing, `@EncodingDefault` encodes the provided expression instead of `nil`, and `@CodingDefault` combines both behaviors with a single annotation.

### 8. Ignore Properties

Use `@CodingIgnored` to ignore specific properties during encoding/decoding. During decoding, non-`Optional` properties must have a default value to satisfy Swift initialization requirements. `ReerCodable` automatically generates default values for basic data types and collection types. For other custom types, users need to provide default values.
Expand Down
24 changes: 24 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,30 @@ enum Gender: String {
}
```

如果需要更精细地控制默认值, 可以使用 `@DecodingDefault`, `@EncodingDefault`, `@CodingDefault`:

```swift
@Decodable
struct Flags {
@DecodingDefault(false)
var isEnabled: Bool
}

@Encodable
struct Payload {
@EncodingDefault("anonymous")
var nickname: String?
}

@Codable
struct Preferences {
@CodingDefault([String]())
var tags: [String]?
}
```

`@DecodingDefault` 会在解码失败或缺失时使用提供的表达式, `@EncodingDefault` 会在编码 `nil` 可选值时使用该表达式, 而 `@CodingDefault` 同时具备前两者的行为。

### 8. 忽略属性

使用 `@CodingIgnored` 在编解码过程中忽略特定属性. 在解码过程中对于非 `Optional` 属性要有一个默认值才能满足 Swift 初始化的要求, `ReerCodable` 对基本数据类型和集合类型会自动生成默认值, 如果是其他自定义类型, 则需用用户提供默认值.
Expand Down
54 changes: 54 additions & 0 deletions Sources/ReerCodable/MacroDeclarations/DefaultValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Copyright © 2024 reers.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

/// Provides a fallback value when decoding fails or keys are missing.
///
/// ```swift
/// @Decodable
/// struct Flags {
/// @DecodingDefault(false)
/// let isEnabled: Bool
/// }
/// ```
/// The decoded value uses the provided expression when decoding throws or when
/// the key is not present.
@attached(peer)
public macro DecodingDefault<Value>(
_ value: Value
) = #externalMacro(module: "ReerCodableMacros", type: "DecodingDefault")

/// Provides a fallback value when encoding optional properties.
///
/// When applied to an optional property, the expression is encoded in place of
/// `nil`, ensuring downstream encoders always see a concrete value.
@attached(peer)
public macro EncodingDefault<Value>(
_ value: Value
) = #externalMacro(module: "ReerCodableMacros", type: "EncodingDefault")

/// Provides a fallback value for both encoding and decoding.
///
/// This macro combines the behavior of `@DecodingDefault` and `@EncodingDefault`
/// for cases where both directions should share the same default.
@attached(peer)
public macro CodingDefault<Value>(
_ value: Value
) = #externalMacro(module: "ReerCodableMacros", type: "CodingDefault")
20 changes: 14 additions & 6 deletions Sources/ReerCodable/TypeConversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

#if canImport(CoreGraphics)
import CoreGraphics
#endif

protocol TypeConvertible {
static func convert(from object: Any) -> Self?
}
Expand Down Expand Up @@ -106,16 +112,18 @@ extension FloatConvertible {

// MARK: - To CGFloat

#if canImport(CoreGraphics) && !os(Linux) && !os(Windows)
import CoreGraphics

#if canImport(CoreGraphics) || os(Linux) || os(Windows)
extension CGFloat: TypeConvertible {
static func convert(from object: Any) -> CGFloat? {
if let string = object as? CustomStringConvertible,
let double = Double(string.description) {
if let convertible = object as? CustomStringConvertible,
let double = Double(convertible.description) {
return CGFloat(double)
} else if let bool = object as? Bool {
return bool ? 1.0 : 0
return CGFloat(bool ? 1 : 0)
} else if let double = object as? Double {
return CGFloat(double)
} else if let float = object as? Float {
return CGFloat(float)
} else {
return nil
}
Expand Down
Loading
Loading