Skip to content

Commit ef5dc1e

Browse files
authored
feat: support @EncodingIgnored and @DecodingIgnored (#40)
1 parent 6f1e0a7 commit ef5dc1e

11 files changed

Lines changed: 383 additions & 47 deletions

File tree

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Main features include:
4040
- Customize nested containers during Coding using `@CodingContainer`
4141
- Support specified `CodingKey` for Encode, like `EncodingKey("encode_key")`
4242
- Allow using default values when decoding fails to avoid `keyNotFound` errors
43-
- Allow using `@CodingIgnored` to ignore specific properties during encoding/decoding
43+
- Allow using `@CodingIgnored`, `@EncodingIgnored`, and `@DecodingIgnored` to ignore specific properties during coding
4444
- Support automatic conversion between base64 strings and `Data` `[UInt8]` types using `@Base64Coding`
4545
- Through `@CompactDecoding`, ignore `null` values when Decoding `Array`, `Dictionary`, `Set` instead of throwing errors
4646
- Support various encoding/decoding of `Date` through `@DateCoding`
@@ -333,15 +333,21 @@ struct Preferences {
333333

334334
### 8. Ignore Properties
335335

336-
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.
336+
Use `@CodingIgnored` to ignore properties during both encoding and decoding, `@EncodingIgnored` to ignore properties only during encoding, and `@DecodingIgnored` to ignore properties only during decoding. When decoding is ignored, 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.
337337

338338
```swift
339339
@Codable
340340
struct User {
341341
var name: String
342-
342+
343343
@CodingIgnored
344-
var ignore: Set<String>
344+
var transient: Set<String>
345+
346+
@EncodingIgnored
347+
var serverToken: String
348+
349+
@DecodingIgnored
350+
var localDraft: String = "draft"
345351
}
346352
```
347353

README_CN.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ ReerCodable 框架提供了一系列自定义宏,用于生成动态的 Codable
3535
- 通过使用 `@CodingContainer` 自定义 Coding 时的嵌套容器
3636
- 支持 Encode 时指定的 `CodingKey`, 如 `EncodingKey("encode_key")`
3737
- 允许解码失败时使用默认值, 从而避免 `keyNotFound` 错误发生
38-
- 允许使用 `@CodingIgnored` 在编解码过程中忽略特定属性
38+
- 允许使用 `@CodingIgnored``@EncodingIgnored``@DecodingIgnored` 在编解码过程中按需忽略特定属性
3939
- 支持使用 `@Base64Coding` 自动对 base64 字符串和 `Data` `[UInt8]` 类型进行转换
4040
- 在 Decode `Array`, `Dictionary`, `Set` 时, 通过 `@CompactDecoding` 可以忽略 `null` 值, 而不是抛出错误
4141
- 支持通过 `@DateCoding` 实现对 `Date` 的各种编解码
@@ -327,15 +327,21 @@ struct Preferences {
327327

328328
### 8. 忽略属性
329329

330-
使用 `@CodingIgnored` 在编解码过程中忽略特定属性. 在解码过程中对于非 `Optional` 属性要有一个默认值才能满足 Swift 初始化的要求, `ReerCodable` 对基本数据类型和集合类型会自动生成默认值, 如果是其他自定义类型, 则需用用户提供默认值.
330+
使用 `@CodingIgnored` 可以同时在编码和解码时忽略属性, `@EncodingIgnored` 只在编码时忽略, `@DecodingIgnored` 只在解码时忽略. 当属性会被解码侧忽略时, 对于非 `Optional` 属性要有一个默认值才能满足 Swift 初始化的要求, `ReerCodable` 对基本数据类型和集合类型会自动生成默认值, 如果是其他自定义类型, 则需由用户提供默认值.
331331

332332
```swift
333333
@Codable
334334
struct User {
335335
var name: String
336-
336+
337337
@CodingIgnored
338-
var ignore: Set<String>
338+
var transient: Set<String>
339+
340+
@EncodingIgnored
341+
var serverToken: String
342+
343+
@DecodingIgnored
344+
var localDraft: String = "draft"
339345
}
340346
```
341347

Sources/ReerCodable/MacroDeclarations/CodingIgnored.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,20 @@
3838
/// ```
3939
@attached(peer)
4040
public macro CodingIgnored() = #externalMacro(module: "ReerCodableMacros", type: "CodingIgnored")
41+
42+
/// The `@EncodingIgnored` macro marks a property to be ignored during encoding only.
43+
///
44+
/// When applied to a property in a type marked with `@Codable`, this property will be:
45+
/// - Skipped during encoding (not written to the encoded data)
46+
/// - Still participate in decoding as usual
47+
@attached(peer)
48+
public macro EncodingIgnored() = #externalMacro(module: "ReerCodableMacros", type: "EncodingIgnored")
49+
50+
/// The `@DecodingIgnored` macro marks a property to be ignored during decoding only.
51+
///
52+
/// When applied to a property in a type marked with `@Codable`, this property will be:
53+
/// - Skipped during decoding (not read from the encoded data)
54+
/// - Still participate in encoding as usual
55+
/// - Initialized with its default value or nil if optional
56+
@attached(peer)
57+
public macro DecodingIgnored() = #externalMacro(module: "ReerCodableMacros", type: "DecodingIgnored")

Sources/ReerCodableMacros/MacroImplementations/CustomCodingImpl.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public struct CustomCoding: PeerMacro {
3434
if variable.attributes.count > 1 {
3535
let incompatibleMacros = [
3636
"CodingIgnored",
37+
"EncodingIgnored",
38+
"DecodingIgnored",
3739
"Base64Coding",
3840
"DateCoding",
3941
"CompactDecoding",

Sources/ReerCodableMacros/MacroImplementations/IgnoreCodingImpl.swift

Lines changed: 96 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,47 +22,109 @@
2222
import SwiftSyntax
2323
import SwiftSyntaxMacros
2424

25+
private enum IgnoreCodingMode: Equatable {
26+
case both
27+
case encoding
28+
case decoding
29+
30+
var macroName: String {
31+
switch self {
32+
case .both:
33+
return "CodingIgnored"
34+
case .encoding:
35+
return "EncodingIgnored"
36+
case .decoding:
37+
return "DecodingIgnored"
38+
}
39+
}
40+
41+
var diagnosticPrefix: String {
42+
"@\(macroName) macro"
43+
}
44+
}
45+
46+
private func validateIgnoredProperty(
47+
declaration: some DeclSyntaxProtocol,
48+
mode: IgnoreCodingMode
49+
) throws {
50+
guard
51+
let variable = declaration.as(VariableDeclSyntax.self),
52+
let name = variable.name
53+
else {
54+
throw MacroError(text: "\(mode.diagnosticPrefix) is only for property.")
55+
}
56+
57+
let ignoreMacros = ["CodingIgnored", "EncodingIgnored", "DecodingIgnored"]
58+
let usedIgnoreMacros = ignoreMacros.filter { variable.attributes.containsAttribute(named: $0) }
59+
if usedIgnoreMacros.count > 1 {
60+
throw MacroError(
61+
text: "\(mode.diagnosticPrefix) cannot be used together with @\(usedIgnoreMacros.filter { $0 != mode.macroName }.joined(separator: ", @"))."
62+
)
63+
}
64+
65+
if mode == .encoding, variable.attributes.containsAttribute(named: "EncodingKey") {
66+
throw MacroError(text: "@EncodingIgnored macro cannot be used together with @EncodingKey.")
67+
}
68+
69+
if mode != .encoding {
70+
if variable.isOptional {
71+
return
72+
}
73+
if variable.initExpr != nil {
74+
return
75+
}
76+
if let type = variable.type,
77+
canGenerateDefaultValue(for: type) {
78+
return
79+
}
80+
throw MacroError(text: "The ignored property `\(name)` should have a default value, or be set as an optional type.")
81+
}
82+
}
83+
2584
public struct CodingIgnored: PeerMacro {
2685
public static func expansion(
2786
of node: AttributeSyntax,
2887
providingPeersOf declaration: some DeclSyntaxProtocol,
2988
in context: some MacroExpansionContext
3089
) throws -> [DeclSyntax] {
31-
guard
32-
let variable = declaration.as(VariableDeclSyntax.self),
33-
let name = variable.name
34-
else {
35-
throw MacroError(text: "@CodingIgnored macro is only for property.")
36-
}
37-
38-
if variable.attributes.firstAttribute(named: "CodingIgnored") != nil {
39-
if variable.isOptional {
40-
return []
41-
}
42-
if variable.initExpr != nil {
43-
return []
44-
}
45-
if let type = variable.type,
46-
canGenerateDefaultValue(for: type) {
47-
return []
48-
}
49-
throw MacroError(text: "The ignored property `\(name)` should have a default value, or be set as an optional type.")
50-
}
90+
try validateIgnoredProperty(declaration: declaration, mode: .both)
5191
return []
5292
}
53-
54-
static func canGenerateDefaultValue(for type: String) -> Bool {
55-
let trimmed = type.trimmingCharacters(in: .whitespaces)
56-
let basicType = [
57-
"Int", "Int8", "Int16", "Int32", "Int64", "Int128",
58-
"UInt", "UInt8", "UInt16", "UInt32", "UInt64", "UInt128",
59-
"Bool", "String", "Float", "Double"
60-
].contains(trimmed)
61-
if basicType
62-
|| (trimmed.hasPrefix("[") && trimmed.hasSuffix("]"))
63-
|| trimmed.hasPrefix("Set<") {
64-
return true
65-
}
66-
return false
93+
}
94+
95+
public struct EncodingIgnored: PeerMacro {
96+
public static func expansion(
97+
of node: AttributeSyntax,
98+
providingPeersOf declaration: some DeclSyntaxProtocol,
99+
in context: some MacroExpansionContext
100+
) throws -> [DeclSyntax] {
101+
try validateIgnoredProperty(declaration: declaration, mode: .encoding)
102+
return []
103+
}
104+
}
105+
106+
public struct DecodingIgnored: PeerMacro {
107+
public static func expansion(
108+
of node: AttributeSyntax,
109+
providingPeersOf declaration: some DeclSyntaxProtocol,
110+
in context: some MacroExpansionContext
111+
) throws -> [DeclSyntax] {
112+
try validateIgnoredProperty(declaration: declaration, mode: .decoding)
113+
return []
114+
}
115+
}
116+
117+
func canGenerateDefaultValue(for type: String) -> Bool {
118+
let trimmed = type.trimmingCharacters(in: .whitespaces)
119+
let basicType = [
120+
"Int", "Int8", "Int16", "Int32", "Int64", "Int128",
121+
"UInt", "UInt8", "UInt16", "UInt32", "UInt64", "UInt128",
122+
"Bool", "String", "Float", "Double"
123+
].contains(trimmed)
124+
if basicType
125+
|| (trimmed.hasPrefix("[") && trimmed.hasSuffix("]"))
126+
|| trimmed.hasPrefix("Set<") {
127+
return true
67128
}
129+
return false
68130
}

Sources/ReerCodableMacros/Plugins.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ struct ReerCodablePlugin: CompilerPlugin {
5454
CodingKey.self,
5555
EncodingKey.self,
5656
CodingIgnored.self,
57+
EncodingIgnored.self,
58+
DecodingIgnored.self,
5759
DecodingDefault.self,
5860
EncodingDefault.self,
5961
CodingDefault.self,

Sources/ReerCodableMacros/PropertyInfo.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ struct PropertyInfo {
2727
var name: String
2828
var type: String
2929
var isOptional = false
30-
var isIgnored = false
30+
var ignoreEncoding = false
31+
var ignoreDecoding = false
3132
var keys: [String] = []
3233
var encodingKey: String?
3334
var treatDotAsNestedWhenEncoding: Bool = true

Sources/ReerCodableMacros/TypeInfo.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,14 @@ extension TypeInfo {
336336
property.caseStyles = propertyCaseStyles.uniqueMerged(with: caseStyles)
337337
// ignore coding
338338
if variable.attributes.firstAttribute(named: "CodingIgnored") != nil {
339-
property.isIgnored = true
339+
property.ignoreEncoding = true
340+
property.ignoreDecoding = true
341+
}
342+
if variable.attributes.firstAttribute(named: "EncodingIgnored") != nil {
343+
property.ignoreEncoding = true
344+
}
345+
if variable.attributes.firstAttribute(named: "DecodingIgnored") != nil {
346+
property.ignoreDecoding = true
340347
}
341348
// base64 coding
342349
if variable.attributes.containsAttribute(named: "Base64Coding") {
@@ -681,7 +688,7 @@ extension TypeInfo {
681688
} else {
682689
assignments = try properties
683690
.compactMap { property in
684-
if property.isIgnored {
691+
if property.ignoreDecoding {
685692
if property.isOptional { return nil }
686693
if let initExpr = property.initExpr {
687694
return "self.\(property.name) = \(initExpr)"
@@ -824,7 +831,7 @@ extension TypeInfo {
824831
} else {
825832
encoding = properties
826833
.compactMap { property in
827-
if property.isIgnored { return nil }
834+
if property.ignoreEncoding { return nil }
828835
let valueExpr = property.encodingValueExpr
829836
let resolvedValueExpr = property.resolvedEncodingValueExpr
830837
let needsOptionalHandling = property.needsOptionalEncodingHandling
@@ -914,7 +921,7 @@ extension TypeInfo {
914921
text += ": \(property.type)"
915922
if let initExpr = property.initExpr {
916923
text += "= \(initExpr)"
917-
} else if property.isIgnored, let defaultValue = property.defaultValue {
924+
} else if property.ignoreDecoding, let defaultValue = property.defaultValue {
918925
text += "= \(defaultValue)"
919926
} else if property.isOptional {
920927
text += "= nil"

0 commit comments

Comments
 (0)