generated from nimblehq/git-template
-
Notifications
You must be signed in to change notification settings - Fork 0
[Feature] Create JSONMapper framework #1
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,47 @@ | ||
*.gem | ||
*.rbc | ||
/.config | ||
/.idea | ||
/coverage/ | ||
/InstalledFiles | ||
/node-modules | ||
/pkg/ | ||
/spec/reports/ | ||
/spec/examples.txt | ||
/test/tmp/ | ||
/test/version_tmp/ | ||
/tmp/ | ||
|
||
# Used by dotenv library to load environment variables. | ||
# .env | ||
|
||
## Documentation cache and generated files: | ||
/.yardoc/ | ||
/_yardoc/ | ||
/doc/ | ||
/rdoc/ | ||
|
||
## Environment normalization: | ||
/.bundle/ | ||
/vendor/bundle | ||
/lib/bundler/man/ | ||
|
||
# for a library or gem, you might want to ignore these files since the code is | ||
# intended to run in multiple environments; otherwise, check them in: | ||
# Gemfile.lock | ||
# .ruby-version | ||
# .ruby-gemset | ||
|
||
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: | ||
.rvmrc | ||
# Created by https://www.gitignore.io/api/swift | ||
# Edit at https://www.gitignore.io/?templates=swift | ||
|
||
### Swift ### | ||
# Xcode | ||
# | ||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore | ||
|
||
## Build generated | ||
build/ | ||
DerivedData/ | ||
|
||
## Various settings | ||
*.pbxuser | ||
!default.pbxuser | ||
*.mode1v3 | ||
!default.mode1v3 | ||
*.mode2v3 | ||
!default.mode2v3 | ||
*.perspectivev3 | ||
!default.perspectivev3 | ||
xcuserdata/ | ||
|
||
## Other | ||
*.moved-aside | ||
*.xccheckout | ||
*.xcscmblueprint | ||
|
||
## Obj-C/Swift specific | ||
*.hmap | ||
*.ipa | ||
*.dSYM.zip | ||
*.dSYM | ||
|
||
## Playgrounds | ||
timeline.xctimeline | ||
playground.xcworkspace | ||
|
||
# Swift Package Manager | ||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. | ||
# Packages/ | ||
# Package.pins | ||
# Package.resolved | ||
.build/ | ||
|
||
# End of https://www.gitignore.io/api/swift | ||
.DS_Store |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// swift-tools-version:5.3 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "JSONMapper", | ||
products: [ | ||
// Products define the executables and libraries a package produces, and make them visible to other packages. | ||
.library( | ||
name: "JSONMapper", | ||
targets: ["JSONMapper"]), | ||
], | ||
dependencies: [ | ||
// Dependencies declare other packages that this package depends on. | ||
// .package(url: /* package url */, from: "1.0.0"), | ||
], | ||
targets: [ | ||
// Targets are the basic building blocks of a package. A target can define a module or a test suite. | ||
// Targets can depend on other targets in this package, and on products in packages this package depends on. | ||
.target( | ||
name: "JSONMapper", | ||
dependencies: []), | ||
.testTarget( | ||
name: "JSONMapperTests", | ||
dependencies: ["JSONMapper"]), | ||
] | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// | ||
// Encodable+Dictionary.swift | ||
// NimbleExtension | ||
// | ||
// Created by Tam Nguyen on 11/17/20. | ||
// Copyright Β© 2020 Nimble. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
public extension Encodable { | ||
|
||
func toDictionary(_ encoder: JSONEncoder = JSONEncoder()) -> [String: Any] { | ||
guard let dictionary = try? JSONSerialization.jsonObject( | ||
with: encoder.encode(self), options: .allowFragments | ||
) as? [String: Any] else { | ||
return [:] | ||
} | ||
return dictionary | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// Optional+Or.swift | ||
// | ||
// Created by Edgars Simanovskis on 27/08/2019. | ||
// Copyright Β© 2018 Nimble. All rights reserved. | ||
// | ||
|
||
public extension Optional { | ||
|
||
var isNil: Bool { | ||
return self == nil | ||
} | ||
|
||
var hasValue: Bool { !isNil } | ||
|
||
func or(_ otherOptional: @autoclosure () throws -> Wrapped?) rethrows -> Wrapped? { | ||
switch self { | ||
case .some(let value): return value | ||
case .none: return try otherOptional() | ||
} | ||
} | ||
|
||
func or(_ otherWrapped: @autoclosure () throws -> Wrapped) rethrows -> Wrapped { | ||
switch self { | ||
case .some(let value): return value | ||
case .none: return try otherWrapped() | ||
} | ||
} | ||
|
||
func resolve(with error: @autoclosure () -> Error) throws -> Wrapped { | ||
switch self { | ||
case .none: throw error() | ||
case .some(let wrapped): return wrapped | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// JSONAPIDecoder.swift | ||
// | ||
// Created by Pirush Prechathavanich on 4/4/18. | ||
// Copyright Β© 2018 Nimble. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
public class JSONAPIDecoder: JSONDecoder { | ||
|
||
private typealias ResourceDictionary = [ResourceIdentifier: Resource] | ||
|
||
private let decoder: JSONEncoder | ||
|
||
init(decoder: JSONEncoder = JSONEncoder()) { | ||
self.decoder = decoder | ||
} | ||
|
||
public override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable { | ||
let jsonAPIObject = try super.decode(JSONAPIObject.self, from: data) | ||
|
||
let includedData = jsonAPIObject.included ?? [] | ||
let dictionary = includedDictionary(from: includedData) | ||
|
||
switch jsonAPIObject.type { | ||
case .data(let data): | ||
return try decode(data, including: dictionary, into: type) | ||
case .meta(let meta): | ||
return try decode(meta, into: type) | ||
case .errors(let errors): | ||
throw errors | ||
} | ||
} | ||
|
||
public func decodeWithMeta<Value: Decodable, Meta: Decodable>( | ||
value valueType: Value.Type, | ||
meta metaType: Meta.Type, | ||
from data: Data | ||
) throws -> (value: Value, meta: Meta) { | ||
let jsonAPIObject = try super.decode(JSONAPIObject.self, from: data) | ||
|
||
let includedData = jsonAPIObject.included ?? [] | ||
let dictionary = includedDictionary(from: includedData) | ||
|
||
switch jsonAPIObject.type { | ||
case .data(let data): | ||
return ( | ||
value: try decode(data, including: dictionary, into: valueType), | ||
meta: try decode(jsonAPIObject.meta ?? .nil, into: metaType) | ||
) | ||
case .meta(let meta): | ||
throw Errors.JSONAPIDecodingError.unableToDecode( | ||
reason: "No data field. Only contains meta: \(meta)" | ||
) | ||
case .errors(let errors): | ||
throw errors | ||
} | ||
} | ||
|
||
} | ||
|
||
// MARK: - Private | ||
|
||
extension JSONAPIDecoder { | ||
|
||
private func decode<T: Decodable>(_ meta: JSON, into type: T.Type) throws -> T { | ||
let data = try decoder.encode(meta) | ||
return try super.decode(type, from: data) | ||
} | ||
|
||
private func decode<T: Decodable>(_ dataType: DataType<Resource>, | ||
including includedDictionary: ResourceDictionary, | ||
into type: T.Type) throws -> T { | ||
switch dataType { | ||
case .single(let resource): | ||
return try decode(resource, including: includedDictionary, into: type) | ||
case .collection(let resources): | ||
return try decodeCollection(of: resources, including: includedDictionary, into: type) | ||
} | ||
} | ||
|
||
private func decode<T: Decodable>(_ resource: Resource, | ||
including includedDictionary: ResourceDictionary, | ||
into type: T.Type) throws -> T { | ||
let dictionary = try resolvedAttributes(of: resource, including: includedDictionary) | ||
let data = try decoder.encode(dictionary) | ||
return try super.decode(type, from: data) | ||
} | ||
|
||
private func decodeCollection<T: Decodable>(of resources: [Resource], | ||
including includedDictionary: ResourceDictionary, | ||
into type: T.Type) throws -> T { | ||
let collection = try resources.compactMap { try resolvedAttributes(of: $0, including: includedDictionary) } | ||
let data = try decoder.encode(collection) | ||
return try super.decode(type, from: data) | ||
} | ||
|
||
private func includedDictionary(from includedData: [Resource]) -> ResourceDictionary { | ||
return includedData.reduce(into: [:]) { dictionary, resource in | ||
let identifier = ResourceIdentifier(id: resource.id, type: resource.type) | ||
dictionary[identifier] = resource | ||
} | ||
} | ||
|
||
private func resolvedAttributes(of resource: Resource, | ||
including includedDictionary: ResourceDictionary) throws -> JSON? { | ||
var attributes = resource.attributes?.nested ?? [:] | ||
attributes[Resource.CodingKeys.id.rawValue] = .string(resource.id) | ||
attributes[Resource.CodingKeys.type.rawValue] = .string(resource.type) | ||
|
||
try resource.relationships?.forEach { key, relationship in | ||
guard let type = relationship.data else { return } | ||
switch type { | ||
case .single(let identifier): | ||
let includedResource = try getResource(from: includedDictionary, for: identifier) | ||
attributes[key] = try resolvedAttributes(of: includedResource, including: includedDictionary) | ||
|
||
case .collection(let identifiers): | ||
let includedAttributes = try identifiers | ||
.map { try getResource(from: includedDictionary, for: $0) } | ||
.compactMap { try resolvedAttributes(of: $0, including: includedDictionary) } | ||
attributes[key] = .array(includedAttributes) | ||
} | ||
} | ||
return .nested(attributes) | ||
} | ||
|
||
private func getResource(from includedDictionary: ResourceDictionary, | ||
for identifier: ResourceIdentifier) throws -> Resource { | ||
guard let resource = includedDictionary[identifier] else { | ||
throw Errors.JSONAPIDecodingError.resourceNotFound(identifier: identifier) | ||
} | ||
return resource | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// DataType.swift | ||
// | ||
// Created by Edgars Simanovskis on 27/08/2019. | ||
// Copyright Β© 2018 Nimble. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
public enum DataType<T: Codable>: Codable { | ||
|
||
case single(T) | ||
case collection([T]) | ||
|
||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.singleValueContainer() | ||
do { | ||
self = try .single(container.decode(T.self)) | ||
} catch DecodingError.typeMismatch { | ||
self = try .collection(container.decode([T].self)) | ||
} | ||
} | ||
|
||
public func encode(to encoder: Encoder) throws { | ||
var container = encoder.singleValueContainer() | ||
switch self { | ||
case .single(let object): try container.encode(object) | ||
case .collection(let array): try container.encode(array) | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.