Git Product home page Git Product logo

genome's Introduction

#Genome

Welcome to Genome! This library is meant to simplify the process of mapping JSON to models by providing a clean and flexible API that binds mapping to their models.

###Polymer

Genome is featured in a networking library that uses genome as its mapping core. Check it out here: Polymer


Version License Platform Platform


Documentation
Initial Setup
Getting Started
Genome Object
      Basic Mapping
      Object Properties
      Object Arrays
      Transforming Values
      Specialized Mapping
      Default Properties
Genome Transformer
      From Json
      To Json
      Response Context
Genome Mapping
      Initialization
            With Representation
            Response Context
      Mapped Objects
            Individual Objects
            Arrays
Debugging
      Mappable Description
      Logging


#Initial Setup

If you wish to install the library manually, make sure to include all of the files in Source. However, I strongly recommend familiarizing yourself with cocoapods.

It is highly recommended that you install Genome through cocoapods. Here is a personal cocoapods reference just in case it may be of use in learning: Cocoapods Setup Guide

Podfile: pod 'Genome'
Import: #import <Genome/Genome.h>

#Getting Started

Let's look at an example of some JSON that we might want to model. Here's what a response from the GitHub API looks like for a GitHubEvent.

[
  {
    "id": 1,
    "url": "https://api.github.com/repos/octocat/Hello-World/issues/events/1",
    "actor": {
      "login": "octocat",
      "id": 1,
      "url": "https://api.github.com/users/octocat",
      "type": "User",
      "site_admin": false
    },
    "event": "closed",
    "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
    "created_at": "2011-04-14T16:00:49Z"
  }
]

Let's take a look at how we might model that in our project.

######ObjC

@interface GHEvent : NSObject

@property (copy, nonatomic) NSInteger identifier;
@property (strong, nonatomic) NSURL *url;
@property (strong, nonatomic) GHUser *actor;
@property (copy, nonatomic) NSString *eventDescription;
@property (copy, nonatomic) NSString *commitId;
@property (strong, nonatomic) NSString *createdAt;

@end

######Swift

class GHEvent : NSObject, GenomeObject {

    var identifier: NSInteger = 0
    var url: NSURL?
    var actor: GHUser?
    var eventDescription: String?
    var commitId: String?
    var createdAt: String?

}

The question is: How do we get that JSON parsed into our models? Well, that's where we define a mapping like so:

######ObjC

@implementation GHEvent

+ (NSDictionary *)mapping {
      NSMutableDictionary *mapping = [NSMutableDictionary dictionary];
      mapping[@"identifier"] = @"id";
      mapping[@"url"] = @"url";
      mapping[@"actor"] = @"actor";
      mapping[@"eventDescription"] = @"event";
      mapping[@"commitId"] = @"commit_id";
      mapping[@"createdAt"] = @"created_at";
      return mapping;
}

@end

######Swift

class GHEvent : NSObject, GenomeObject {

    var identifier: NSInteger = 0
    var url: NSURL?
    var actor: GHUser?
    var eventDescription: String?
    var commitId: String?
    var createdAt: String?

    class func mapping() -> [NSObject : AnyObject]! {
        var mapping: [String : String] = [:]
        mapping["identifier"] = "id"
        // Swift seems to be unable to convert string to url automatically
        mapping["url@StringToUrlTransformer"] = "url"
        mapping["actor"] = "actor"
        mapping["eventDescription"] = "event"
        mapping["commitId"] = "commit_id"
        mapping["createdAt"] = "created_at"
        return mapping
    }

}

Now our object knows how to map itself. For the example above, let's imagine that our GHUser object declared as actor is also a GenomeObject. The system will automatically infer and map that object accordingly. Read further or check out the documentation to learn how much more you can do! For this example, let's create our mapped object:

######ObjC

GHEvent *event = [GHEvent gm_mappedObjectWithJsonRepresentation:githubEventJson];

######Swift

let event = GHEvent.gm_mappedObjectWithJsonRepresentation(githubEventJson)

#Genome Object

The core functionality of this library is contained in the mapping method that is declared on classes wishing to conform to GenomeObject. You can however implement a great deal of customizations as necessary for particular situations. This will demonstrate some of that behavior

##Basic Mapping

The basic syntax of the mapping dictionary is as follows:

######ObjC

mapping[@"<#propertyKeyPath#>"] = @"<#associatedJsonKeyPath#>";

######Swift

mapping["<#propertyKeyPath#>"] = "<#associatedJsonKeyPath#>"

If you're unfamiliar with keyPath syntax, you can read about Key-Value Coding here. At it's simplest, it can be thought of as:

mapping["propertyName"] = "associatedJsonKey"

However, by utilizing key paths, we can create more complex behavior. For example, if we have a JSON response that looks like this:

[
  "name" : "Jimbo",
  "address" : [
    "country" : "us",
    "street" : "main",
    // ...
  ]
]

If we had a user and we only wanted their name and country, the model would look like this:

class User : NSObject, GenomeObject {
  var name: String?
  var country: String?

  class func mapping() -> [NSObject : AnyObject]! {
      var mapping: [String : String] = [:]
      mapping["name"] = "name"
      mapping["country"] = "address.country"
      return mapping
  }
}

By specifying the countries JSON path as address.country, we fetch the value at that key path and our object would populate properly.

###Object Properties

It's not uncommon to receive nested Json like our GitHub example in Getting Started. Let's refresh ourselves with the event model:

class GHEvent : NSObject, GenomeObject {

    var identifier: NSInteger = 0
    var url: NSURL?
    var actor: GHUser?
    var eventDescription: String?
    var commitId: String?
    var createdAt: String?

    class func mapping() -> [NSObject : AnyObject]! {
        var mapping: [String : String] = [:]
        mapping["identifier"] = "id"
        mapping["url@StringToUrlTransformer"] = "url"
        mapping["actor"] = "actor"
        mapping["eventDescription"] = "event"
        mapping["commitId"] = "commit_id"
        mapping["createdAt"] = "created_at"
        return mapping
    }

}

You'll notice above that the property actor is declared as a GHUser type. We haven't seen it yet, but this class as well is a GenomeObject. Here's its implementation:

class GHUser : NSObject, GenomeObject {

    var login: String?
    var identifier: NSInteger = 0
    var apiUrl: NSURL?
    var type: String?
    var admin: Bool = false

    class func mapping() -> [NSObject : AnyObject]! {
        var mapping: [String : String] = [:]
        mapping["login"] = "login"
        mapping["identifier"] = "id"
        mapping["apiUrl@StringToUrlTransformer"] = "url"
        mapping["type"] = "type"
        mapping["admin"] = "site_admin"
        return mapping
    }
}

Because this object also conforms to GenomeObject protocol, it will be automatically inferred that the object should be mapped and it will be populated appropriately. This is done through introspection at runtime, but you can also declare the class a property should be mapped to through the following syntax:

mapping[@"<#arrayPropertyName#>@<#ClassName#>"] = @"<#associatedJsonKeyPath#>";

This may speed up mapping slightly if you're looking to optimize the process. If you'd like to use the built in helper, you can declare mapped properties like such:

######ObjC

mapping[gm_propertyMap("actor", [GHUser class])] = @"actor";

######Swift

mapping[gm_propertyMap("actor", GHUser.self)] = @"actor";

This mapping strips the name space in Swift in its current implementation. This means that if multiple libraries within your project conform to GenomeObject AND have the same class name, there may be problems. If you want to use classes in the different namespaces, make sure to ONLY declare mappings explicitly via the gm_propertyMap() function. Otherwise the system can't infer the mapping.

###Object Arrays

It was mentioned above that properties defined as conforming to GenomeObject are inferred and mapped at runtime. Unfortunately, it's not possible to infer what model that array will hold via objective-c runtime. In these situations, you'll need to specify what class the array should be mapped to. If you're worried about it, it is suggested to use the gm_propertyMap function specified above.

######ObjC

mapping[gm_propertyMap(@"array", [MyModel class])] = @"jsonArrayPath";

-- Or --

mapping[@"array@MyModel"] = @"jsonArrayPath";

######Swift

mapping[gm_propertyMap("array", MyModel.self)] = "jsonArrayPath";

-- Or --

mapping["array@MyModel"] = "jsonArrayPath";

###Transforming Values

Quite often when receiving Json there are values we'd like to transform. Some common examples of this are converting an ISO8601 string to an NSDate, or a Hex String to a UIColor. In Genome, this is done by creating classes that conform to GenomeTransformer. Let's go back to our event model to look at an example. Here's a refresher of the raw JSON:

[
  {
    "id": 1,
    "url": "https://api.github.com/repos/octocat/Hello-World/issues/events/1",
    "actor": {
      // ... actor values
    },
    "event": "closed",
    "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
    "created_at": "2011-04-14T16:00:49Z"
  }
]

As we can see, created_at corresponds to an ISO8601 date format. It would be nice if we could make a transformer to convert these strings to an NSDate in a way that can easily be used across all of our models needing conversion. Look at the Genome Transformer section below, or check the documentation

###Specialized Mapping

If you need a different mapping depending on whether or not the operation is from or to json, you can override mappingForOperation: instead. This allows greater flexibility in defining your mapping.

##Default Properties

If genome finds nil or NSNull for a given Json key path, you can use this to define a default value that should exist. It is defined through the following syntax:

defaults["<#propertyName#>"] = "<#defaultValue#>"

#Genome Transformer

The transformer is a class designed to make transforming values from one type to another easy to implement without repeating a lot of code across our models. We will demonstrate how to convert a date string to an NSDate; however, you can override this method anytime you find a situation where the mapping's current implementation is not practical.

Note: No mapping operations will occur if a transformer is provided. This means that whatever you return in a transformer will be set directly to the property (assuming non-null). This means if your transformation is dependent on subsequent mappings, these will need to be called within the transformer.

##From JSON

Let's look at an extremely basic implementation of a GenomeTransformer.

######ObjC

ISO8601DateTransformer.h

@interface ISO8601DateTransformer : GenomeTransformer
@end

ISO8601DateTransformer.m

@implementation ISO8601DateTransformer

+ (id)transformFromJsonValue:(id)fromVal {
  return [NSDate dateWithISO8601String:fromVal];
}

@end

######Swift

class ISO8601DateTransformer : GenomeTransformer {
    override class func transformFromJsonValue(fromVal: AnyObject) -> AnyObject? {
        if let dateString = fromVal as? String {
            return NSDate.dateWithISO8601String(dateString)
        } else {
            return nil
        }
    }
}

This would now replace or GHEvent object so that it looks like this:

######ObjC

@implementation GHEvent

+ (NSDictionary *)mapping {
      // other mappings
      mapping[@"createdAt@ISO8601DateTransformer"] = @"created_at";
      return mapping;
}

@end

######Swift

class GHEvent : NSObject, GenomeObject {

    // ... other properties
    var createdAt: NSDate?

    class func mapping() -> [NSObject : AnyObject]! {
        var mapping: [String : String] = [:]
        // ... other mappings
        mapping["createdAt@ISO8601DateTransformer"] = "created_at"
        return mapping
    }

}

Remember that if you're worried about name spacing, you could use the propertyMap function like so: mapping[gm_propertyMap("createdAt", ISO8601DateTransformer.self)]

####Warning

You'll notice that our transformers are declared with the same mapping syntax we use to specify types for an object property. These are not to be used together, and a transformer always takes precedence. It is assumed that if you need to call a transformer that will eventually result in a mapping, you will need to call the mapping operation manually within the transformer using: gm_mappedObjectWithJsonRepresentation: for objects and gm_mapToGenomeObjectClass: for arrays respectively.

##To Json

Sometimes we'll need to convert our object back to Json. If that's the case, you'll also want to provide a reverse transformer by overriding transformToJsonValue:. If we wanted to convert our NSDate back to an ISO8601 date string above, our full date string transformer would look like this:

######ObjC

@implementation ISO8601DateTransformer

+ (id)transformFromJsonValue:(id)fromVal {
  return [NSDate dateWithISO8601String:fromVal];
}

+ (id)transformToJsonValue:(id)fromVal {
  return [(NSDate *)fromVal iso8601String];
}

@end

######Swift

class ISO8601DateTransformer : GenomeTransformer {
    override class func transformFromJsonValue(fromVal: AnyObject) -> AnyObject? {
        if let dateString = fromVal as? String {
            return NSDate.dateWithISO8601String(dateString)
        } else {
            return nil
        }
    }
    override class func transformToJSONValue(fromVal: AnyObject) -> AnyObject? {
        if let date = fromVal as? NSDate {
            return date.iso8601String
        } else {
            return nil
        }
    }
}

##Response Context

For advanced or specialized transformer behavior, we provide an additional hook when parsing from JSON. This takes the form of transformFromJsonValue:inResponseContext: and it passes in the greatest context initialized. This means that when a property is being initialized, it can have access to the greater context.

#Genome Mapping

##Initialization

By default, genome will call alloc] init]; on an object before mapping the JSON to it. In some situations, particularly when interfacing with core data, a more specific initialization is required. In these situations, you can override gm_newInstance.

Note: This will happen BEFORE the object is mapped.

####With Representation

Sometimes you might need access to the surrounding Json being used to initialize, if that's the case, you can override gm_newInstanceForJsonRepresentation:.

Note: Again, this will happen BEFORE the object is mapped and you should not override this method to do the entirety of the mapping.

####Response Context

If you're parsing a large JSON response, sub-objects will receive the global response context for specialized behavior. You can also access this response context during initialization.

Note: Again, this will happen BEFORE the object is mapped and you should not override this method to do the entirety of the mapping. Yes, I realize the redundancy, but sometimes people skip along and it's helpful to repeat oneself.

##Mapped Objects

To initialize an object with a JSON Representation, you should use the following methods.

###Individual Objects

An individual object is mapped using gm_mappedObjectWithJsonRepresentation:. You can call it like so once your model is properly declared:

######ObjC

GHEvent *event = [GHEvent gm_mappedObjectWithJsonRepresentation:eventJson];

######Swift

let event = GHEvent.gm_mappedObjectWithJsonRepresentation(eventJson);

To convert these objects back to JSON, you can use the following:

######ObjC

NSDictionary *eventJson = [event gm_jsonRepresentation];

######Swift

let eventJson: [NSObject : AnyObject] = event.gm_jsonRepresentation()

###Arrays

For arrays, you should use the methods declared in NSArray+GenomeMapping.h. For mapping from JSON, you should use gm_mapToGenomeObjectClass and pass the class to map each object to:

######ObjC

NSArray *events = [eventsJsonArray gm_mapToGenomeObjectClass:[GHEvent class]];

######Swift

let events: [GHEvent] = eventsJsonArray.gm_mapToGenomeObjectClass(GHEvent.self) as? [GHEvent] ?? []

As with individual objects, arrays can be converted back to Json as well using gm_mapToJSONRepresentation:

######ObjC

NSArray *eventsJson = [events gm_mapToJSONRepresentation];

######Swift

let eventsJsonArray: [AnyObject] = (events as NSArray).gm_mapToJSONRepresentation()

##Debugging

###Mappable Description

You can use gm_mappableDescription to help when debugging. It prints our the current values for properties specified in mapping. It is called like so:

######ObjC

[ob gm_mappableDescription];

######Swift

ob.gm_mappableDescription()

###Logging

In NSObject+GenomeMapping.m set the flag on static BOOL LOG = NO; to YES in order to enable logging.

Feel free to browse the Documentation for more information.

Bitdeli Badge

genome's People

Contributors

bitdeli-chef avatar jeffreyjackson avatar loganwright avatar

Watchers

 avatar  avatar

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.