Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make Any support Codable, like: [String: Any], [Any] #7

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Build and Test

on:
push:
branches: [ master ]
branches: [ master, develop ]
pull_request:
branches: [ master ]
branches: [ master, develop ]
workflow_dispatch:

jobs:
Expand Down
4 changes: 2 additions & 2 deletions ExCodable.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ Pod::Spec.new do |s|
# ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
s.name = "ExCodable"
# export LIB_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)
s.version = ENV["LIB_VERSION"] || "0.5.0"
s.version = ENV["LIB_VERSION"] || "1.0.0-alpha"
s.summary = "Key-Mapping Extensions for Swift Codable"
# s.description = "Key-Mapping Extensions for Swift Codable."
s.homepage = "https://github.com/iwill/ExCodable"

# ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
s.license = "MIT"
s.author = { "Mr. Ming" => "i+ExCodable@iwill.im" }
s.author = { "Mr. Míng" => "i+ExCodable@iwill.im" }
s.social_media_url = "https://iwill.im/about/"

# ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 Mr. Ming
Copyright (c) 2022 Mr. Míng

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
[![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg)](https://swift.org/)
[![Swift Package Manager](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager/)
[![Platforms](https://img.shields.io/cocoapods/p/ExCodable.svg)](#readme)
<br />
[![Build and Test](https://github.com/iwill/ExCodable/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/iwill/ExCodable/actions/workflows/build-and-test.yml)
[![GitHub Releases (latest SemVer)](https://img.shields.io/github/v/release/iwill/ExCodable.svg?sort=semver)](https://github.com/iwill/ExCodable/releases)
[![Deploy to CocoaPods](https://github.com/iwill/ExCodable/actions/workflows/deploy_to_cocoapods.yml/badge.svg)](https://github.com/iwill/ExCodable/actions/workflows/deploy_to_cocoapods.yml)
[![Cocoapods](https://img.shields.io/cocoapods/v/ExCodable.svg)](https://cocoapods.org/pods/ExCodable)
<br />
[![LICENSE](https://img.shields.io/github/license/iwill/ExCodable.svg)](https://github.com/iwill/ExCodable/blob/master/LICENSE)
[![@minglq](https://img.shields.io/twitter/url?url=https%3A%2F%2Fgithub.com%2Fiwill%2FExCodable)](https://twitter.com/minglq)

Expand All @@ -17,14 +19,15 @@ En | [中文](https://iwill.im/ExCodable/)
- [Features](#features)
- [Usage](#usage)
- [Requirements](#requirements)
- [Migration Guides](#migration-guides)
- [Installation](#installation)
- [Credits](#credits)
- [License](#license)

## Features

- Extends Swift `Codable` - `Encodable & Decodable`;
- Supports Key-Mapping via `KeyPath` and Coding-Key:
- Supports Key-Mapping via Property-Wrapper `ExCodable` + `String`:
- `ExCodable` did not read/write memory via unsafe pointers;
- No need to encode/decode properties one by one;
- Just requires using `var` to declare properties and provide default values;
Expand Down Expand Up @@ -57,7 +60,7 @@ struct TestAutoCodable: Codable, Equatable {

```

But, if you have to encode/decode manually for some reason, e.g. Alternative-Keys and Nested-Keys ...
But, if you have to encode/decode manually for some reason, e.g. Default-Value, Alternative-Keys, Nested-Keys or Type-Conversions ...

```swift
struct TestManualCodable: Equatable {
Expand Down Expand Up @@ -414,7 +417,7 @@ pod 'ExCodable', '~> 0.5.0'

- John Sundell ([@JohnSundell](https://github.com/JohnSundell)) and the ideas from his [Codextended](https://github.com/JohnSundell/Codextended)
- ibireme ([@ibireme](https://github.com/ibireme)) and the features from his [YYModel](https://github.com/ibireme/YYModel)
- Mr. Ming ([@iwill](https://github.com/iwill)) | i+ExCodable@iwill.im
- Mr. Míng ([@iwill](https://github.com/iwill)) | i+ExCodable@iwill.im

## License

Expand Down
151 changes: 151 additions & 0 deletions Sources/ExCodable/DecodingContainer+AnyCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//
// DecodingContainer+AnyCollection.swift
// AnyDecodable
//
// Created by levantAJ on 1/18/19.
// Copyright © 2019 levantAJ. All rights reserved.
//
// https://github.com/levantAJ/AnyCodable
import Foundation

struct AnyCodingKey: CodingKey {
var stringValue: String
var intValue: Int?

init?(stringValue: String) {
self.stringValue = stringValue
}

init?(intValue: Int) {
self.intValue = intValue
self.stringValue = String(intValue)
}
}

extension KeyedDecodingContainer {
/// Decodes a value of the given type for the given key.
///
/// - parameter type: The type of value to decode.
/// - parameter key: The key that the decoded value is associated with.
/// - returns: A value of the requested type, if present for the given key
/// and convertible to the requested type.
/// - throws: `DecodingError.typeMismatch` if the encountered encoded value
/// is not convertible to the requested type.
/// - throws: `DecodingError.keyNotFound` if `self` does not have an entry
/// for the given key.
/// - throws: `DecodingError.valueNotFound` if `self` has a null entry for
/// the given key.
public func decode(_ type: [Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [Any] {
var values = try nestedUnkeyedContainer(forKey: key)
return try values.decode(type)
}

/// Decodes a value of the given type for the given key.
///
/// - parameter type: The type of value to decode.
/// - parameter key: The key that the decoded value is associated with.
/// - returns: A value of the requested type, if present for the given key
/// and convertible to the requested type.
/// - throws: `DecodingError.typeMismatch` if the encountered encoded value
/// is not convertible to the requested type.
/// - throws: `DecodingError.keyNotFound` if `self` does not have an entry
/// for the given key.
/// - throws: `DecodingError.valueNotFound` if `self` has a null entry for
/// the given key.
public func decode(_ type: [String: Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [String: Any] {
let values = try nestedContainer(keyedBy: AnyCodingKey.self, forKey: key)
return try values.decode(type)
}

/// Decodes a value of the given type for the given key, if present.
///
/// This method returns `nil` if the container does not have a value
/// associated with `key`, or if the value is null. The difference between
/// these states can be distinguished with a `contains(_:)` call.
///
/// - parameter type: The type of value to decode.
/// - parameter key: The key that the decoded value is associated with.
/// - returns: A decoded value of the requested type, or `nil` if the
/// `Decoder` does not have an entry associated with the given key, or if
/// the value is a null value.
/// - throws: `DecodingError.typeMismatch` if the encountered encoded value
/// is not convertible to the requested type.
public func decodeIfPresent(_ type: [Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [Any]? {
guard contains(key),
try decodeNil(forKey: key) == false else { return nil }
return try decode(type, forKey: key)
}

/// Decodes a value of the given type for the given key, if present.
///
/// This method returns `nil` if the container does not have a value
/// associated with `key`, or if the value is null. The difference between
/// these states can be distinguished with a `contains(_:)` call.
///
/// - parameter type: The type of value to decode.
/// - parameter key: The key that the decoded value is associated with.
/// - returns: A decoded value of the requested type, or `nil` if the
/// `Decoder` does not have an entry associated with the given key, or if
/// the value is a null value.
/// - throws: `DecodingError.typeMismatch` if the encountered encoded value
/// is not convertible to the requested type.
public func decodeIfPresent(_ type: [String: Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [String: Any]? {
guard contains(key),
try decodeNil(forKey: key) == false else { return nil }
return try decode(type, forKey: key)
}
}

private extension KeyedDecodingContainer {
func decode(_ type: [String: Any].Type) throws -> [String: Any] {
var dictionary: [String: Any] = [:]
for key in allKeys {
if try decodeNil(forKey: key) {
dictionary[key.stringValue] = NSNull()
} else if let bool = try? decode(Bool.self, forKey: key) {
dictionary[key.stringValue] = bool
} else if let string = try? decode(String.self, forKey: key) {
dictionary[key.stringValue] = string
} else if let int = try? decode(Int.self, forKey: key) {
dictionary[key.stringValue] = int
} else if let double = try? decode(Double.self, forKey: key) {
dictionary[key.stringValue] = double
} else if let dict = try? decode([String: Any].self, forKey: key) {
dictionary[key.stringValue] = dict
} else if let array = try? decode([Any].self, forKey: key) {
dictionary[key.stringValue] = array
}
}
return dictionary
}
}

private extension UnkeyedDecodingContainer {
mutating func decode(_ type: [Any].Type) throws -> [Any] {
var elements: [Any] = []
while !isAtEnd {
if try decodeNil() {
elements.append(NSNull())
} else if let int = try? decode(Int.self) {
elements.append(int)
} else if let bool = try? decode(Bool.self) {
elements.append(bool)
} else if let double = try? decode(Double.self) {
elements.append(double)
} else if let string = try? decode(String.self) {
elements.append(string)
} else if let values = try? nestedContainer(keyedBy: AnyCodingKey.self),
let element = try? values.decode([String: Any].self) {
elements.append(element)
} else if var values = try? nestedUnkeyedContainer(),
let element = try? values.decode([Any].self) {
elements.append(element)
}
}
return elements
}
mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> {
let nestedContainer = try self.nestedContainer(keyedBy: AnyCodingKey.self)
return try nestedContainer.decode(type)
}
}
131 changes: 131 additions & 0 deletions Sources/ExCodable/EncodingContainer+AnyCollection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//
// EncodingContainer+AnyCollection.swift
// AnyDecodable
//
// Created by ShopBack on 1/19/19.
// Copyright © 2019 levantAJ. All rights reserved.
//
// https://github.com/levantAJ/AnyCodable
import Foundation

extension KeyedEncodingContainer {
/// Encodes the given value for the given key.
///
/// - parameter value: The value to encode.
/// - parameter key: The key to associate the value with.
/// - throws: `EncodingError.invalidValue` if the given value is invalid in
/// the current context for this format.
public mutating func encode(_ value: [String: Any], forKey key: KeyedEncodingContainer<K>.Key) throws {
var container = nestedContainer(keyedBy: AnyCodingKey.self, forKey: key)
try container.encode(value)
}

/// Encodes the given value for the given key.
///
/// - parameter value: The value to encode.
/// - parameter key: The key to associate the value with.
/// - throws: `EncodingError.invalidValue` if the given value is invalid in
/// the current context for this format.
public mutating func encode(_ value: [Any], forKey key: KeyedEncodingContainer<K>.Key) throws {
var container = nestedUnkeyedContainer(forKey: key)
try container.encode(value)
}

/// Encodes the given value for the given key if it is not `nil`.
///
/// - parameter value: The value to encode.
/// - parameter key: The key to associate the value with.
/// - throws: `EncodingError.invalidValue` if the given value is invalid in
/// the current context for this format.
public mutating func encodeIfPresent(_ value: [String: Any]?, forKey key: KeyedEncodingContainer<K>.Key) throws {
if let value = value {
var container = nestedContainer(keyedBy: AnyCodingKey.self, forKey: key)
try container.encode(value)
} else {
try encodeNil(forKey: key)
}
}

/// Encodes the given value for the given key if it is not `nil`.
///
/// - parameter value: The value to encode.
/// - parameter key: The key to associate the value with.
/// - throws: `EncodingError.invalidValue` if the given value is invalid in
/// the current context for this format.
public mutating func encodeIfPresent(_ value: [Any]?, forKey key: KeyedEncodingContainer<K>.Key) throws {
if let value = value {
var container = nestedUnkeyedContainer(forKey: key)
try container.encode(value)
} else {
try encodeNil(forKey: key)
}
}
}

private extension KeyedEncodingContainer where K == AnyCodingKey {
mutating func encode(_ value: [String: Any]) throws {
for (k, v) in value {
let key = AnyCodingKey(stringValue: k)!
switch v {
case is NSNull:
try encodeNil(forKey: key)
case let string as String:
try encode(string, forKey: key)
case let int as Int:
try encode(int, forKey: key)
case let bool as Bool:
try encode(bool, forKey: key)
case let double as Double:
try encode(double, forKey: key)
case let dict as [String: Any]:
try encode(dict, forKey: key)
case let array as [Any]:
try encode(array, forKey: key)
default:
debugPrint("⚠️ Unsuported type!", v)
continue
}
}
}
}

private extension UnkeyedEncodingContainer {
/// Encodes the given value.
///
/// - parameter value: The value to encode.
/// - throws: `EncodingError.invalidValue` if the given value is invalid in
/// the current context for this format.
mutating func encode(_ value: [Any]) throws {
for v in value {
switch v {
case is NSNull:
try encodeNil()
case let string as String:
try encode(string)
case let int as Int:
try encode(int)
case let bool as Bool:
try encode(bool)
case let double as Double:
try encode(double)
case let dict as [String: Any]:
try encode(dict)
case let array as [Any]:
var values = nestedUnkeyedContainer()
try values.encode(array)
default:
debugPrint("⚠️ Unsuported type!", v)
}
}
}

/// Encodes the given value.
///
/// - parameter value: The value to encode.
/// - throws: `EncodingError.invalidValue` if the given value is invalid in
/// the current context for this format.
mutating func encode(_ value: [String: Any]) throws {
var container = self.nestedContainer(keyedBy: AnyCodingKey.self)
try container.encode(value)
}
}
Loading