Code Monkey home page Code Monkey logo

genome's Introduction

Welcome to Genome 3.0. This library seeks to satisfy the following goals:

  • Data Type Agnostic
  • Failure Driven
  • Nested Mapping
  • Collection Mapping
  • Simple and Consistent
  • Two-Way Serialization
  • Transforms
  • Type Safety
  • Constants (let)
  • Full Linux Support
  • Struct Friendly
  • Inheritance Friendly
  • Core Data and Persistence Compatible

Node

Genome is built on top of Node as opposed to JSON directly. This makes it easy for Genome to work with any data type through little effort.

All mapping operations are built as sugar on top of Node's core.

Optimized For JSON

Works great w/ JSON out of the box by default:

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    guard let data = data else { return }
    do {
        let model = try Model(node: data)
        completion(model)
    } catch {
        completion(error)
    }
}
task.resume()

If your data is nested, you can use Node to take it further.

let json = try rawJSONData.makeNode()
guard let items = json["root", "items"] else { return }
let models = try [Item](node: items)

You'll notice above that we used initialized an array, this is all perfectly great w/ Genome.

If you're working on Linux with SwiftPM, it is highly recommended to use a type-safe JSON library like this one.

To JSON

We can create our JSON Data the same way:

let jsonData = try Data(node: item)
api.post(jsonData) { response in ... }

Building Project

All future development of Cocoapods will be done on with SwiftPM. Cocoapods and Carthage support are intended to be maintained, but are not used in development. Here are some useful commands

# make xcode project
swift package generate-xcodeproj
# build project
swift build
# test project
swift test

SwiftPM

To use SwiftPM, add this to your Package.swift

.Package(url: "https://github.com/LoganWright/Genome.git", majorVersion: 3)

Cocoapods

pod 'Genome', '~> 3.0'

Carthage

github "LoganWright/Genome"

Table Of Contents

Quick Start

Let's take the following hypothetical JSON

[
    "name" : "Rover",
    "nickname" : "RoRo", // Optional Value
    "type" : "dog"
]

Here's how we might create the model for this

enum PetType: String {
    case dog
    case cat
    case unknown
}

struct Pet: MappableObject {
    let name: String
    let type: PetType
    let nickname: String?

    init(map: Map) throws {
        name = try map.extract("name")
        nickname = try map.extract("nickname")
        type = try map.extract("type") { PetType(rawValue: $0) ?? .unknown }
    }

    func sequence(map: Map) throws {
        try name ~> map["name"]
        try type ~> map["type"].transformToNode { $0.rawValue }
        try nickname ~> map["nickname"]
    }
}

Once that's done, we can build like so:

let pet = try Pet(node: json)

It will also work with collections:

let pets = try [Pet](node: jsonArray)

NASA Photo

Let's build a simple example that fetches NASA's photo of the day. Please note that this is a synchronous API, and it makes use of Data for brevity. It is advisable to use an asynchronous and proper HTTP Client like URLSession.

struct Photo: BasicMappable {
    private(set) var title: String = ""
    private(set) var mediaType: String = ""
    private(set) var explanation: String = ""
    private(set) var concepts: [String] = []

    private(set) var imageUrl: NSURL!

    mutating func sequence(_ map: Map) throws {
        try title <~ map["title"]
        try mediaType <~ map ["media_type"]
        try explanation <~ map["explanation"]
        try concepts <~ map["concepts"]
        try imageUrl <~ map["url"]
            .transformFromNode { NSURL(string: $0) }
    }
}

struct NASA {
    static let url = URL(string: "https://api.nasa.gov/planetary/apod?concept_tags=True&api_key=DEMO_KEY")!

    static func fetchPhoto() throws -> Photo {
        let data = try Data(contentsOf: NASA.url)
        return try Photo(node: data)
    }
}

Now we can call like this:

let photo = try NASA.fetchPhoto()

WARNING: Please read first paragraph regarding synchronicity and api.

MappableObject

This is one of the core protocol options for this library. It will be the go to for most standard mapping operations.

It has two requirements

init(map: Map) throws

This is the initializer you will use to map your object. You may call this manually if you like, but if you use any of the built in convenience initializers, this will be called automatically. Otherwise, if you need to initialize a Map, use:

let map = Map(node: someNode, in: someContext)

It has two main requirements

sequence(map: Map) throws

The sequence function is called in two main situations. It is marked mutating because it will modify values on fromNode operations. If however, you're only using sequence for toNode, nothing will be mutated and one can remove the mutating keyword. (as in the above example)

FromNode

When mapping to Node w/ any of the convenience initializer. After instantiating the object, sequence will be called. This allows objects that don't initialize constants or objects that use the two-way operator to complete their mapping.

If you are initializing w/ init(map: Map) directly, you will be responsible for calling sequence manually if your object requires it.

It is marked mutating because it will modify values.

Note, if you're only mapping to Node, nothing will be mutated.

ToNode

When accessing an objects makeNode(), the sequence operation will be called to collect the values into a Node package.

~>

This is one of the main operations used in this library. The ~ symbolizes a connection, and the < and > respectively symbol a flow of value. When declared as ~> it symbolizes that mapping only happens from value, to Node.

You could also use the following:

Operator Directions Example Mutates
<~> To and From Node try name <~> map["name"] โœ“
~> To Node Only try clientId ~> map["client_id"] ๐˜…
<~ From Node Only try updatedAt <~ map["updated_at"] โœ“

transform

Genome provides various options for transforming values. These are type-safe and will be checked by the compiler.

These are chainable, like the following:

try type <~> map["type"]
    .transformFromNode {
        return PetType(rawValue: $0)
    }
    .transformToNode {
        return $0.rawValue
    }

Note: At the moment, transforms require absolute optionality conformance in some situations. ie, Optionals get Optionals, ImplicitlyUnwrappedOptionals get ImplicitlyUnwrappedOptionals, etc.

fromNode

When using let constants, you will need to call a transformer that sets the value instantly. In this case, you will call fromNode and pass any closure that takes a NodeConvertibleType (a standard Node type) and returns a value.

transformFromNode

Use this if you need to transform the node input to accomodate your type. In our example above, we need to convert the raw node to our associated enum. This can also be appended to mappings for the <~ operator.

transformToNode

Use this if you need to transform the given value to something more suitable for data. This can also be appended to mappings for the ~> operator.

try

Why is the try keyword on every line! Every mapping operation is failable if not properly specified. It's better to deal with these possibilities, head first.

For example, if the property being set is non-optional, and nil is found in the Node, the operation should throw an error that can be easily caught.

More Concepts

Some of the different functionality available in Genome

The way that Genome is constructed, you should never have to deal w/ Node beyond deserializing and serializing for your web services. It can still be used directly if desired.

Inheritance

Genome is most suited to final classes and structures, but it does support Inheritance. Unfortunately, due to some limitations surrounding generics, protocols, and Self it requires some extra effort.

Object

The Object type is provided by the library to satisfy most inheritance based mapping operations. Simply subclass Object and you're good to go:

class MyClass : Object {}

Note: If you're using Realm, or another library that has also used Object, don't forget that these are module namespaced in Swift. If that's the case, you should declare your class: class MyClass : Genome.Object {}

BasicMappable

In order to support flexible customization, Genome provides various mapping options for protocols. Your object can conform to any of the following. Although each of these initializers is marked with throws, it is not necessary for your initializer to throw if it is guaranteed to succeed. In that case, you can omit the throws keyword safely.

Protocol Required Initializer
BasicMappable init() throws
MappableObject init(map: Map) throws

These are all just convenience protocols, and ultimately all derive from MappableBase. If you wish to define your own implementation, the rest of the library's functionality will still apply.

NodeConvertibleType

This is the true root of the library. Even MappableBase mentioned above inherits from this core type. It has two requirements:

public protocol NodeConvertibleType {
    init(node: Node, in context: Context) throws
    func makeNode(context: Context) throws -> Node
}

All basic types such as Int, String, etc. conform to this protocol which allows ultimate flexibility in defining the library. It also paves the way to much fewer overloads going forward when collections of NodeConvertible can also conform to it.

Instantiation

If you are using the standard instantiation scheme established in the library, you will likely initialize with this function.

public init(node: Node, in context: Context = EmptyNode) throws

Now we can easily create an object safely:

do {
    let rover = try Pet(node: nodeRover)
    print(rover)
} catch {
    print(error)
}

If all we care about is whether or not we were able to create an object, we can also do the following:

let rover = try? Pet(node: nodeRover)
print(rover) // Rover is type: `Pet?`

Context

Context is defined as an empty protocol that any object you might need access to can conform to and passed within.

Foundation

If you're using Foundation, you can transform Any, [String: Any], and [Any] types by making them into a Node first. Node(any: yourTypeHere).

CollectionTypes

You can instantiate collections directly w/o mapping as well:

let people = try [People](node: someNode)

Core Data

If you wish to use CoreData, instead of subclassing NSManagedObject, subclass ManagedObject.

Happy Mapping!

genome's People

Contributors

bitdeli-chef avatar brightredchilli avatar iliaskarim avatar jeffreyjackson avatar joannis avatar justbaum30 avatar loganwright avatar marxon13 avatar mau888 avatar maxhasadhd avatar patoroco avatar revolter avatar t-unit avatar wanewang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

genome's Issues

'String' is not convertible to 'KeyType'

Hello,

first of all: great library. I just moved from another lib to Genome, and I find it much more transparent and efficient.

My problem: I want to save a UIImage, using this code in the sequence function:

        try image <~> map["image"]
            .transformFromJson {
                let stringData = $0
                let image = UIImage.fromBase64(stringData)
                return UIImage(data: imageData)
        }
            .transformToJson {
                let image: UIImage = $0
                let stringData = image.toBase64()
                return stringData
        }

However, this does not compile, as the error "'String' is not convertible to 'KeyType'" is thrown. What goes wrong, and how to fix it?

Thank a lot in advance!

  • Hardy

Dealing with optional variables (basic types and enum)

I am learning how to use Genome, and so far I am impressed with the flexibility offered by Genome. ๐Ÿ‘

  1. Is it possible to declare optional variables (both basic type and enum) that will not be mapped to JSON at all if they are nil?
  2. An extension to this question is how do I specify the transformToNode and transformFromNode functions for these optional variables?
  3. I am trying to extend Date class, making it NodeRepresentable and NodeConvertible. (See code below). Would this implementation be able to handle when date is nil during serialization (my intention is to skip saving date data if date is nil) and when the JSON data is absent during deserialization (my intention is to leave the date variable uninitialized)?
extension Date: NodeRepresentable {
    public func makeNode(context: Context = EmptyNode) throws -> Node {
        return .string(self.ISO8601String())
    }
}

extension Date: NodeConvertible {
    public init(node: Node, in context: Context) throws {
        guard let string = node.string else {
            throw NodeError.unableToConvert(node: node, expected: "\(String.self)")
        }
        guard let date = Date.dateFromISO8601String(string: string) else {
            throw NodeError.unableToConvert(node: node, expected: "Date is stored in ISO8601 format")
        }
        self = date
    }
}

Example class definition:

enum FooType: Int8 {
    case typeA
    case typeB
}

class Bar: BasicMappable {
    var foo: FooType?
    var date: Date?
    var name: String?
    
    required init() {
    }
    
    func sequence(map: Map) throws {
        try foo <~> map["foo"]
        try date <~> map["date"]
        try name <~> map["name"]
    }
}

Problem with restoring data

I have a custom data structure, with DM_ModelRoot as root item. Writing to a JSON string is working fine:

let map = Map()
var settableRoot: DM_ModelRoot? = self.modelRoot

try settableRoot ~> map["modelRoot"]

print(settableRoot)

var jsonString = map.toJson.serialize()

However, when trying to restore the JSON string, there is an error UnableToMap(.KeyPath(modelRoot), Genome.SequenceError.FoundNil("Key: .KeyPath(identifier) TargetType: String")):

var settableRoot: DM_ModelRoot?

let json = try Json.deserialize(jsonString)

let map = Map(json: json)

try settableRoot <~ map["modelRoot"]

What am I doing wrong?

Note: DM_ModelRoot is setup with init(...) and sequence(...) properly, the JSON string is created correctly.

BTW: It would be helpful to have examples for correctly reading and writing JSON from and into custom objects on the documentation page ;-)

Core Data initialization

Can you provide us how to use Genome for core data?
Which protocol/class we have to use?

According to your Readme: NSMappableManagedObject is not avaiable.

Node alternative to Json.deserialize(string) and Json.serialize(style)?

Hi!
Yesterday I serialized and deserialized strings to and from JSON directly with PureJsonSerializer like this:

Json.deserialize(rawString)
and
Json.serialize(aStyle)

but know I'm a little but confused as to how to do it with Node. Could you give us some insight on this please?

Thanks!

null value under KeyPath

Hi,
Awesome library! I'm trying to figure out how to handle a case when a given field may contain null value. Right now I receive

UnexpectedInputType("Unexpectedly found nil input.  KeyPath: .KeyPath(Date2) Expected: String")

which is quite understandable given it contains null value.
How should I approach it?

Swift 3: Date unconditionallyBridgeFromObjectiveC exception

I am using Genome to parse server response to CoreData entity. Exception is thrown when non-optional Date field is parsed:
libswiftFoundation.dylib`static Foundation.Date._unconditionallyBridgeFromObjectiveC (Swift.Optional<__ObjC.NSDate>) -> Foundation.Date:

extension BaseNote {
    @NSManaged var date: Date
    @NSManaged var text: String?
}

override func sequence(_ map: Map) throws {
//...
    try date <~> map["date"]
        .transformFromNode {
            return Date(timeIntervalSince1970: $0)
        }
        .transformToNode { return $0.timeIntervalSince1970 }
}

Note that the problem disappears if I use NSDate in NSManagedObject subclass or make the field optional (Date?). It worked fine before migration to Swift 3.

Please correct me if I'm doing it wrong.

Problems running playground

Hi,

I'm having some troubles trying to run playground. I need to drag Genome.xcodeproj inside of Genome.playground > Sources folder because if I don't do it, it has problems finding MappableObject and other requirements in playground.

Do you know if I'm doing something wrong? I usually don't use playgrounds, so maybe it's a problem of mine...

sub-map node not change when mapping

I would like to have a sub-map when mapping so I don't have to type the path repeatedly
e.x. json is

[
    "test": "abc",
    "sub": [
        "int": 9,
        "string": "8"
    ]
]

and my mapping function is

try mainString <~> map["test"]
let subMap = map["sub"]
try subInt <~> subMap["int"]
try subString <~> subMap["string"]

but when I do let subMap = map["sub"]
the subMap.node is still original node since subscript only change result

// file Map.swift line 102
public subscript(keys: [PathIndex]) -> Map {
    lastPath = keys
    result = node[keys]
    return self
}

should it also change node to node[keys]?

Create a method for converting null strings to empty strings on map.

Instead of doing something like

extension String {
    static func fromJson(json: Json) -> String {
        return json.stringValue ?? ""
    }
}
func sequence(map: Map) throws {
    try someValue <~ map["someValue"].transformFromJson(String.fromJson)
}

Add a method like: <~ map["someValue"].emptyStringTransform()

Simple Customizable Logging

Possibilities

var logOutput: ((ErrorType) -> Void)? = print

Or, an array:

var logOutput: [(ErrorType) -> Void)] = [print]

Updated CoreData Example

I'm not sure exactly how to use Genome with core Data. Am I missing something here? How do they get initialized?

How do I fetch or init it?

public class Todo: ManagedObject {
    
    init(map: Map, context: Context) throws {
        try checked ~> map["checked"]
        try id ~> map["id"]
        try text ~> map["text"]
    }

    public override func sequence(_ map: Map) throws {
        try checked ~> map["checked"]
        try id ~> map["id"]
        try text ~> map["text"]
    }
       
}

version 3.1.0 not work in Carthage

I'm trying Genome with Carthage, but when running carthage update it won't download Packages folder content. Node, PathIndexable and Polymorphic is missing in project, which caused built failed.

I'm now running with specific version
github "LoganWright/Genome" == 3.0.3
It worked.

might be submodule issue?

User Defaults

I am attempting to set a Genome.Node.array value to user defaults. Is this even possible via Genome? I have looked into extending Genome and Node to conform to NSCoding but have not found a unsuccessful as they are not classes.

Suggestions? Thank you.

Error: method 'newInstance(_:context:)' in non-final class 'ChecklistItem' must return `Self` to conform to protocol 'JsonConvertibleType'

I'm getting this error using Carthage install: github "LoganWright/Genome" "2.0.4"

Here is the source code

class ChecklistItem: MappableObject {

    var title: String
    var checked: Bool

    init(title: String, checked: Bool) {
        self.title = title
        self.checked = checked
    }

    convenience init(title: String) {
        self.init(title: title, checked: false)
    }

    required init(map: Map) throws {
        self.title = try map.extract("title")
        self.checked = try map.extract("checked")
    }

    func sequence(map: Map) throws {
        try title ~> map["title"];
        try checked ~> map["checked"];
    }

}

extension ChecklistItem: Equatable { }
func == (lhs: ChecklistItem, rhs: ChecklistItem) -> Bool {
    return lhs.checked == rhs.checked && lhs.title == rhs.title
}

Add copyright

Hi,
Could you please add a copyright statement to the license?
Thank you!

transformToJson to NSNull

// Genome 1.0
try self.note ~> map["note"]
  .transformToJson {
    $0 ?? NSNull()
  }
// Genome 2.0
try self.note ~> map["note"]
  .transformToJson { (value: String?) -> String in
    if let text = value {
      return text
    }
    return "\(NSNull())"
  }

In Genome 1.0, I can transformToJson from nil to NSNull by first code block
but In Genome 2.0, I have to change the transform code to second block which does not actually return NSNull as I want but a String

does there have some way to do the function like before?

Core Data example in readme

I've tried to play with Genome 2.0 and CoreData example and faced with complier error:

error: declarations from extensions cannot be overridden yet

Here is example without CoreData stack to reduce code size.

public class NSManagedObjectContextStub {}
public class NSManagedObjectObjectStub {}

extension NSManagedObjectContextStub: Context {}

extension NSManagedObjectObjectStub: MappableBase {
    public class var entityName: String {
        return "\(self)"
    }

    public func sequence(map: Map) throws {
        fatalError("Sequence must be overwritten")
    }

    public class func newInstance(json: Json, context: Context) throws -> Self {
        return try newInstance(json, context: context, type: self)
    }

    public class func newInstance<T: NSManagedObjectObjectStub>(json: Json, context: Context, type: T.Type) throws -> T {
        let context = context as! NSManagedObjectContextStub
        let new = NSManagedObjectObjectStub() as! T
        let map = Map(json: json, context: context)
        try new.sequence(map)
        return new
    }
}

class Person: NSManagedObjectObjectStub {
    override func sequence(map: Map) throws {
        // error: declarations from extensions cannot be overridden yet
    }
}

Can't build with Carthage

I tried building Genome 3.0.0 with Carthage, but I couldn't build with error 'Dependency "Genome" has no shared framework schemes for any of the platforms: iOS'.

Update Podspec

Latest version when adding with cocoapods is Installing Genome (3.0.2). Not latest (3.1.1)

jsonRepresentation can not cast to AnyObject

In v1.0.8
I can do this directly(obj is a class object confirm to BasicMappable)

  var basic = [String: AnyObject]()
  basic["obj"] = obj.jsonRepresentation()

But in v2, it won't work and I can't even cast to AnyObject

if let dict = try obj.jsonRepresentation() as? AnyObject {
  basic["obj"] = dict
}

I'm using Alamofire to do a POST which take Dictionary<String, AnyObject> as parameter and assemble to string automatically, does there a way to do things like before?

Int64 unexpected value mapping error

Could you describe how to use Genome with Int64 type? I'm getting error while trying to map:

"UnexpectedValue("Found: 42 ofType: __NSCFNumber Expected: Int64 KeyPath: id TargetType: Int64")\n"

Here is sample:

let string = "{ \"id\": 42 }"
let data = string.dataUsingEncoding(NSUTF8StringEncoding)
let json = try? NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)

struct Sample: BasicMappable {
    private(set) var id: Int64 = 0
    mutating func sequence(map: Map) throws {
        try id <~> map["id"]
    }
}

do {
    let sample = try Sample.mappedInstance(json as! JSON)
    print(sample.id)
} catch {
    print(error)
}

tvOS support

import Genome currently results in the following build error on Xcode 7.1 beta 3:

module file was created for incompatible target x86_64-apple-ios8.0: [...]/Carthage/Build/iOS/Genome.framework/Modules/Genome.swiftmodule/x86_64.swiftmodule

It would be great to see this project support tvOS like Alamofire and Decodable have!

Core Data documentation

Hi guys, your framework look promising and very clear! I would like to ask extend Core data support documentation. Especially what I'm interesting in is: how to get some keys from JSON in init section to link with existing objects in CD and not to create new one?

Swift 3 / Xcode 8 / Carthage: usable version available soon?

Are there plans to have a working version of this very nice project soon that can be used with Swift 3 and be checkout out via Carthage?

I had a look at #58 but current state doesn't even contain a Xcode project. The Swift 3 branch over at PureJsonSerializer also seems very outdated.

Would be cool to have and working Swift 3 version soon. It's the one thing holding me back from upgrading to Xcode 8.

Include map key in error message

I remember that 2.0 has key information in error message right?
like if let id:String = map["id"], but map["id"] is nil,
the message will have the key id in it.
It would be better for debugging with the key information.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.