HappyCodable.Common 1.0.0

HappyCodable.Common 1.0.0

mikun 维护。



  • mikun

HappyCodable

使用 SourceKittenFramework 通过自动创建 Codable 相关代码来让 Codable 更愉快。

什么是 Codable 的问题?

  1. 不支持对单个编码键的更改,一旦更改一个编码键,就需要设置所有编码键。
  2. 不支持忽略特定的编码键。
  3. 不支持为非 RawRepresentable 枚举自动生成代码,即使每个元素都是可编码的。
  4. 不支持多个键映射到一个属性。
  5. 调试困难。
  6. 当 JSON 数据中缺少相应键时,不自动使用定义中的默认值。
  7. 不支持 0/1 到 false/true

用法

  1. 构建 HappyCodable 命令行可执行文件
  2. 将可执行文件和 HappyCodable Common 源代码引入您的项目中
  3. 编译源代码 之前在 构建阶段 中添加一个运行脚本,如下所示
${SRCROOT}/HappyCodableCommandLine ${SRCROOT}/Classes ${SRCROOT}/HappyCodable.generated.swift
  1. HappyCodable 添加到一个结构体或类中
struct Person: HappyCodable {
	var name1: String = "abc"
	
	@Happy.codingKeys("🍉")
	var numberOfTips2: String = "abc"
	
	@Happy.codingKeys("234", "age", "abc") // the first key will be the coding key
	var age: String = "abc"
	
	@Happy.uncoding
	var abc: String = "abc" // Build fail if coded, in this case, we can "uncoding" it.
}

然后 HappyCodableCommandLine 会自动创建代码

extension Person {
	enum CodingKeys: String, CodingKey {
		case name1
		case numberOfTips2 = "🍉"
		case age = "234"
	}
	mutating
	func decode(happyFrom decoder: Decoder) throws {
		let container = try decoder.container(keyedBy: CodingKeys.self)
		
		if Self.globalDecodeAllowOptional {
			do { self.name1 = try decoder.decode(defaultValue: self.name1, verifyValue: self.name1, forKey: .name1, alterKeys: [], from: container) } catch { }
			do { self.numberOfTips2 = try decoder.decode(defaultValue: self.numberOfTips2, verifyValue: self.numberOfTips2, forKey: .numberOfTips2, alterKeys: [], from: container) } catch { }
			do { self.age = try decoder.decode(defaultValue: self.age, verifyValue: self.age, forKey: .age, alterKeys: ["age", "abc"], from: container) } catch { }
		} else {
			self.name1 = try decoder.decode(defaultValue: self.name1, verifyValue: self.name1, forKey: .name1, alterKeys: [], from: container)
			self.numberOfTips2 = try decoder.decode(defaultValue: self.numberOfTips2, verifyValue: self.numberOfTips2, forKey: .numberOfTips2, alterKeys: [], from: container)
			self.age = try decoder.decode(defaultValue: self.age, verifyValue: self.age, forKey: .age, alterKeys: ["age", "abc"], from: container)
		}
		
	}
	func encode(happyTo encoder: Encoder) throws {
		var container = encoder.container(keyedBy: CodingKeys.self)
		if Self.globalEncodeSafely {
			do { try container.encodeIfPresent(self.name1, forKey: .name1) } catch { }
			do { try container.encodeIfPresent(self.numberOfTips2, forKey: .numberOfTips2) } catch { }
			do { try container.encodeIfPresent(self.age, forKey: .age) } catch { }
		} else {
			try container.encode(self.name1, forKey: .name1)
			try container.encode(self.numberOfTips2, forKey: .numberOfTips2)
			try container.encode(self.age, forKey: .age)
		}
	}
}

也支持非 RawRepresentable 枚举

enum EnumTest: HappyCodableEnum {
	case value(num: Int, name: String)
//	case call(() -> Void) // Build fails if added, since (() -> Void) can't be codable
	case name0(String)
	case name1(String, last: String)
	case name2(first: String, String)
	case name3(_ first: String, _ last: String)
}

生成的代码

extension EnumTest {
	init(from decoder: Decoder) throws {
		let container = try decoder.singleValueContainer()
		let content = try container.decode([String: [String: String]].self)
		let error = DecodingError.typeMismatch(EnumTest.self, DecodingError.Context(codingPath: [], debugDescription: ""))
		guard let name = content.keys.first else {
			throw error
		}
		let decoder = JSONDecoder()
		switch name {
			case ".value(num:name:)":
				guard
					let _0 = content[name]?["num"]?.data(using: .utf8),
					let _1 = content[name]?["name"]?.data(using: .utf8)
				else {
					throw error
				}
				
				self = .value(
					num: try decoder.decode((Int).self, from: _0),
					name: try decoder.decode((String).self, from: _1)
				)
			case ".name0(_:)":
				guard
					let _0 = content[name]?["$0"]?.data(using: .utf8)
				else {
					throw error
				}
				
				self = .name0(
					try decoder.decode((String).self, from: _0)
				)
			case ".name1(_:last:)":
				guard
					let _0 = content[name]?["$0"]?.data(using: .utf8),
					let _1 = content[name]?["last"]?.data(using: .utf8)
				else {
					throw error
				}
				
				self = .name1(
					try decoder.decode((String).self, from: _0),
					last: try decoder.decode((String).self, from: _1)
				)
			case ".name2(first:_:)":
				guard
					let _0 = content[name]?["first"]?.data(using: .utf8),
					let _1 = content[name]?["$1"]?.data(using: .utf8)
				else {
					throw error
				}
				
				self = .name2(
					first: try decoder.decode((String).self, from: _0),
					try decoder.decode((String).self, from: _1)
				)
			case ".name3(_:_:)":
				guard
					let _0 = content[name]?["first"]?.data(using: .utf8),
					let _1 = content[name]?["last"]?.data(using: .utf8)
				else {
					throw error
				}
				
				self = .name3(
					try decoder.decode((String).self, from: _0),
					try decoder.decode((String).self, from: _1)
				)
		default:
			throw error
		}
	}
	func encode(to encoder: Encoder) throws {
		var container = encoder.singleValueContainer()
		let encoder = JSONEncoder()
		switch self {
			case let .value(_0, _1):
				try container.encode([
					".value(num:name:)": [
						"num": String(data: try encoder.encode(_0), encoding: .utf8),
						"name": String(data: try encoder.encode(_1), encoding: .utf8)
					]
				])
			case let .name0(_0):
				try container.encode([
					".name0(_:)": [
						"$0": String(data: try encoder.encode(_0), encoding: .utf8)
					]
				])
			case let .name1(_0, _1):
				try container.encode([
					".name1(_:last:)": [
						"$0": String(data: try encoder.encode(_0), encoding: .utf8),
						"last": String(data: try encoder.encode(_1), encoding: .utf8)
					]
				])
			case let .name2(_0, _1):
				try container.encode([
					".name2(first:_:)": [
						"first": String(data: try encoder.encode(_0), encoding: .utf8),
						"$1": String(data: try encoder.encode(_1), encoding: .utf8)
					]
				])
			case let .name3(_0, _1):
				try container.encode([
					".name3(_:_:)": [
						"first": String(data: try encoder.encode(_0), encoding: .utf8),
						"last": String(data: try encoder.encode(_1), encoding: .utf8)
					]
				])
		}
	}
}