├── .gitignore ├── .travis.yml ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── RxCodable.podspec ├── Sources ├── RxCodable │ ├── Maybe+RxCodable.swift │ ├── ObservableType+RxCodable.swift │ └── Single+RxCodable.swift └── TestUtil │ └── Fixture.swift ├── Tests └── RxCodableTests │ └── RxCodableTests.swift └── codecov.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | **/xcuserdata 6 | **/xcshareddata 7 | Pods/ 8 | Carthage/ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode10.2 2 | language: objective-c 3 | sudo: required 4 | env: 5 | global: 6 | - PROJECT="RxCodable.xcodeproj" 7 | - SCHEME="RxCodable-Package" 8 | - IOS_SDK="iphonesimulator12.2" 9 | - MACOS_SDK="macosx10.14" 10 | - TVOS_SDK="appletvsimulator12.2" 11 | - WATCHOS_SDK="watchsimulator5.2" 12 | - FRAMEWORK="RxCodable" 13 | matrix: 14 | - SDK="$IOS_SDK" TEST=1 DESTINATION="platform=iOS Simulator,name=iPhone XS,OS=12.2" 15 | - SDK="$MACOS_SDK" TEST=1 DESTINATION="arch=x86_64" 16 | - SDK="$TVOS_SDK" TEST=1 DESTINATION="OS=12.2,name=Apple TV 4K" 17 | - SDK="$WATCHOS_SDK" TEST=0 DESTINATION="OS=5.2,name=Apple Watch Series 4 - 44mm" 18 | 19 | install: 20 | - eval "$(curl -sL https://gist.githubusercontent.com/kylef/5c0475ff02b7c7671d2a/raw/9f442512a46d7a2af7b850d65a7e9bd31edfb09b/swiftenv-install.sh)" 21 | - swift --version 22 | 23 | before_script: 24 | - set -o pipefail 25 | - swift package generate-xcodeproj 26 | 27 | script: 28 | - if [ $TEST == 1 ]; then 29 | xcodebuild clean build test 30 | -project "$PROJECT" 31 | -scheme "$SCHEME" 32 | -sdk "$SDK" 33 | -destination "$DESTINATION" 34 | -configuration Debug 35 | -enableCodeCoverage YES 36 | CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c; 37 | else 38 | xcodebuild clean build 39 | -project "$PROJECT" 40 | -scheme "$SCHEME" 41 | -sdk "$SDK" 42 | -destination "$DESTINATION" 43 | -configuration Debug 44 | CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c; 45 | fi 46 | 47 | after_success: 48 | - if [ $TEST == 1 ]; then 49 | bash <(curl -s https://codecov.io/bash) -X xcodeplist -J 'RxCodable'; 50 | fi 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Suyeol Jeon (xoul.kr) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "RxSwift", 6 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "b3e888b4972d9bc76495dd74d30a8c7fad4b9395", 10 | "version": "5.0.1" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "RxCodable", 7 | platforms: [ 8 | .macOS(.v10_11), .iOS(.v8), .tvOS(.v9), .watchOS(.v3) 9 | ], 10 | products: [ 11 | .library(name: "RxCodable", targets: ["RxCodable"]), 12 | ], 13 | dependencies: [ 14 | .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "5.0.0")), 15 | ], 16 | targets: [ 17 | .target(name: "RxCodable", dependencies: ["RxSwift"]), 18 | .target(name: "TestUtil", dependencies: ["RxCodable", "RxBlocking"]), 19 | .testTarget(name: "RxCodableTests", dependencies: ["RxCodable", "TestUtil"]), 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxCodable 2 | 3 | ![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg) 4 | [![CocoaPods](http://img.shields.io/cocoapods/v/RxCodable.svg)](https://cocoapods.org/pods/RxCodable) 5 | [![Build Status](https://travis-ci.org/devxoul/RxCodable.svg?branch=master)](https://travis-ci.org/devxoul/RxCodable) 6 | [![codecov](https://img.shields.io/codecov/c/github/devxoul/RxCodable.svg)](https://codecov.io/gh/devxoul/RxCodable) 7 | 8 | RxSwift wrapper for Codable. 9 | 10 | ## At a Glance 11 | 12 | ```swift 13 | struct User: Codable { 14 | var name: String 15 | } 16 | 17 | // Data -> User 18 | Observable.just(jsonData).map(User.self) 19 | Single.just(jsonData).map(User.self) 20 | Maybe.just(jsonData).map(User.self) 21 | 22 | // String -> User 23 | Observable.just(jsonString).map(User.self) 24 | Single.just(jsonString).map(User.self) 25 | Maybe.just(jsonString).map(User.self) 26 | ``` 27 | 28 | ## Installation 29 | 30 | * **Using [CocoaPods](https://cocoapods.org)**: 31 | 32 | ```ruby 33 | pod 'RxCodable' 34 | ``` 35 | 36 | ## Contributing 37 | 38 | Any discussions and pull requests are welcomed 💖 39 | 40 | To create a Xcode project: 41 | 42 | ```console 43 | $ swift package generate-xcodeproj 44 | ``` 45 | 46 | ## License 47 | 48 | RxCodable is under MIT license. See the [LICENSE](LICENSE) for more info. 49 | -------------------------------------------------------------------------------- /RxCodable.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "RxCodable" 3 | s.version = "1.0.0" 4 | s.summary = "RxSwift wrapper for Codable" 5 | s.homepage = "https://github.com/devxoul/RxCodable" 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = { "Suyeol Jeon" => "devxoul@gmail.com" } 8 | s.source = { :git => "https://github.com/devxoul/RxCodable.git", 9 | :tag => s.version.to_s } 10 | 11 | s.swift_version = "5.0" 12 | s.ios.deployment_target = "8.0" 13 | s.osx.deployment_target = "10.11" 14 | s.tvos.deployment_target = "9.0" 15 | s.watchos.deployment_target = "3.0" 16 | 17 | s.default_subspec = "Core" 18 | 19 | s.subspec "Core" do |ss| 20 | ss.source_files = "Sources/RxCodable/**/*.swift" 21 | ss.frameworks = "Foundation" 22 | ss.dependency "RxSwift", "~> 5.0" 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /Sources/RxCodable/Maybe+RxCodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | 4 | public extension PrimitiveSequenceType where Trait == MaybeTrait, Element == Data { 5 | func map(_ type: T.Type, using decoder: JSONDecoder? = nil) -> PrimitiveSequence where T: Decodable { 6 | return self.map { data -> T in 7 | let decoder = decoder ?? JSONDecoder() 8 | return try decoder.decode(type, from: data) 9 | } 10 | } 11 | } 12 | 13 | public extension PrimitiveSequenceType where Trait == MaybeTrait, Element == String { 14 | func map(_ type: T.Type, using decoder: JSONDecoder? = nil) -> PrimitiveSequence where T: Decodable { 15 | return self 16 | .map { string in string.data(using: .utf8) ?? Data() } 17 | .map(type, using: decoder) 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Sources/RxCodable/ObservableType+RxCodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | 4 | public extension ObservableType where Element == Data { 5 | func map(_ type: T.Type, using decoder: JSONDecoder? = nil) -> Observable where T: Decodable { 6 | return self.map { data -> T in 7 | let decoder = decoder ?? JSONDecoder() 8 | return try decoder.decode(type, from: data) 9 | } 10 | } 11 | } 12 | 13 | public extension ObservableType where Element == String { 14 | func map(_ type: T.Type, using decoder: JSONDecoder? = nil) -> Observable where T: Decodable { 15 | return self 16 | .map { string in string.data(using: .utf8) ?? Data() } 17 | .map(type, using: decoder) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/RxCodable/Single+RxCodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | 4 | public extension PrimitiveSequenceType where Trait == SingleTrait, Element == Data { 5 | func map(_ type: T.Type, using decoder: JSONDecoder? = nil) -> PrimitiveSequence where T: Decodable { 6 | return self.map { data -> T in 7 | let decoder = decoder ?? JSONDecoder() 8 | return try decoder.decode(type, from: data) 9 | } 10 | } 11 | } 12 | 13 | public extension PrimitiveSequenceType where Trait == SingleTrait, Element == String { 14 | func map(_ type: T.Type, using decoder: JSONDecoder? = nil) -> PrimitiveSequence where T: Decodable { 15 | return self 16 | .map { string in string.data(using: .utf8) ?? Data() } 17 | .map(type, using: decoder) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/TestUtil/Fixture.swift: -------------------------------------------------------------------------------- 1 | public struct User: Codable, Equatable { 2 | public let id: Int 3 | public let name: String 4 | 5 | public init(id: Int, name: String) { 6 | self.id = id 7 | self.name = name 8 | } 9 | 10 | public enum CodingKeys: String, CodingKey { 11 | case id 12 | case name 13 | } 14 | 15 | public static func == (lhs: User, rhs: User) -> Bool { 16 | return lhs.id == rhs.id && lhs.name == rhs.name 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/RxCodableTests/RxCodableTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import RxBlocking 3 | import RxCodable 4 | import RxSwift 5 | import TestUtil 6 | 7 | class RxCodableTests: XCTestCase { 8 | func testMapCodableFromJSONData() { 9 | let jsonString = """ 10 | { 11 | "id": 123, 12 | "name": "devxoul" 13 | } 14 | """ 15 | let jsonData = jsonString.data(using: .utf8) ?? Data() 16 | let user = User(id: 123, name: "devxoul") 17 | XCTAssertEqual(try Observable.just(jsonData).map(User.self).toBlocking().first()!, user) 18 | XCTAssertEqual(try Single.just(jsonData).map(User.self).toBlocking().first()!, user) 19 | XCTAssertEqual(try Maybe.just(jsonData).map(User.self).toBlocking().first()!, user) 20 | } 21 | 22 | func testMapCodableArrayFromJSONData() { 23 | let jsonString = """ 24 | [ 25 | { 26 | "id": 123, 27 | "name": "devxoul" 28 | }, 29 | { 30 | "id": 456, 31 | "name": "hello" 32 | } 33 | ] 34 | """ 35 | let jsonData = jsonString.data(using: .utf8) ?? Data() 36 | let users = [ 37 | User(id: 123, name: "devxoul"), 38 | User(id: 456, name: "hello"), 39 | ] 40 | XCTAssertEqual(try Observable.just(jsonData).map([User].self).toBlocking().first()!, users) 41 | XCTAssertEqual(try Single.just(jsonData).map([User].self).toBlocking().first()!, users) 42 | XCTAssertEqual(try Maybe.just(jsonData).map([User].self).toBlocking().first()!, users) 43 | } 44 | 45 | func testMapCodableFromJSONString() { 46 | let jsonString = """ 47 | { 48 | "id": 123, 49 | "name": "devxoul" 50 | } 51 | """ 52 | let user = User(id: 123, name: "devxoul") 53 | XCTAssertEqual(try Observable.just(jsonString).map(User.self).toBlocking().first()!, user) 54 | XCTAssertEqual(try Single.just(jsonString).map(User.self).toBlocking().first()!, user) 55 | XCTAssertEqual(try Maybe.just(jsonString).map(User.self).toBlocking().first()!, user) 56 | } 57 | 58 | func testMapCodableArrayFromJSONString() { 59 | let jsonString = """ 60 | [ 61 | { 62 | "id": 123, 63 | "name": "devxoul" 64 | }, 65 | { 66 | "id": 456, 67 | "name": "hello" 68 | } 69 | ] 70 | """ 71 | let users = [ 72 | User(id: 123, name: "devxoul"), 73 | User(id: 456, name: "hello"), 74 | ] 75 | XCTAssertEqual(try Observable.just(jsonString).map([User].self).toBlocking().first()!, users) 76 | XCTAssertEqual(try Single.just(jsonString).map([User].self).toBlocking().first()!, users) 77 | XCTAssertEqual(try Maybe.just(jsonString).map([User].self).toBlocking().first()!, users) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Tests/" 3 | 4 | coverage: 5 | status: 6 | project: no 7 | patch: no 8 | changes: no 9 | --------------------------------------------------------------------------------