<commit_before>[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Crust.svg)](https://img.shields.io/cocoapods/v/Crust.svg)
[![Build Status](https://travis-ci.org/rexmas/Crust.svg)](https://travis-ci.org/rexmas/Crust)

#Crust
A flexible Swift framework for converting classes and structs to and from JSON with support for storage solutions such as Realm.

#Features 🎸
- [Structs and Classes](#structs-and-classes)
- [Separation of Concerns (Mapped Model, Mapping, Storage)](#separation-of-concerns)
- [Type safe JSON](#jsonvalue-for-type-safe-json)
- [How To Map](#how-to-map)
  - [Nested Mappings](#nested-mappings)
  - [Mapping Context](#mapping-context)
  - [Custom Transformations](#custom-transformations)
  - [Different Mappings for Same Model](#different-mappings-for-same-model)
- [Storage Adaptor](#storage-adaptor)
- [Realm](#realm)
- Supports Optional Types and Collections.

#Requirements
iOS 8.0+
Swift 3.0+

#Installation
### CocoaPods
```
platform :ios, '8.0'
use_frameworks!

pod 'Crust'
```

#Structs and Classes
Can map to/from classes or structs
```swift
class Company {
    var employees = Array<Employee>()
    var uuid: String = ""
    var name: String = ""
    var foundingDate: NSDate = NSDate()
    var founder: Employee?
    var pendingLawsuits: Int = 0
}
```
If you have no need for storage, which will generaly be the case for structs, use `AnyMappable`.
```swift
struct Person: AnyMappable {
    var bankAccounts: Array<Int> = [ 1234, 5678 ]
    var attitude: String = "awesome"
    var hairColor: HairColor = .Unknown
    var ownsCat: Bool? = nil
}
```

#Separation of Concerns

By design Crust is built with [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) in mind. It makes no assumptions about how many ways a user would like to map to and from JSON and how many various ways the user would like to store their models.

Crust has 2 basic protocols:
- `Mapping`
	- How to map JSON to and from a particular model - (model is set by the `associatedtype MappedObject` if mapping to an sequence of objects set `associatedtype SequenceKind`).
	- May include primary key(s) and nested mapping(s).
- `Adaptor`
	- How to store and retrieve model objects used for mapping from a backing store (e.g. Core Data, Realm, etc.).

And 2 additional protocols when no storage `Adaptor` is required:
- `AnyMappable`
	- Inherited by the model (class or struct) to be mapped to and from JSON.
- `AnyMapping`
	- A `Mapping` that does not require an `Adaptor`.

There are no limitations on the number of various `Mapping`s and `Adaptor`s one may create per model for different use cases.

#JSONValue for type safe JSON
Crust relies on [JSONValue](https://github.com/rexmas/JSONValue) for it's JSON encoding and decoding mechanism. It offers many benefits including type safety, subscripting, and extensibility through protocols.

#How To Map

1. Create your mappings for your model using `Mapping` if with storage or `AnyMapping` if without storage.

    With storage (assume `CoreDataAdaptor` conforms to `Adaptor`)
    ```swift
    class EmployeeMapping: Mapping {
    
        var adaptor: CoreDataAdaptor
        var primaryKeys: [PrimaryKeyDescriptor]? {
            // property == attribute on the model, keyPath == keypath in the JSON blob, transform == tranform to apply to data from JSON blob.
            return [ (property: "uuid", keyPath: "data.uuid", transform: nil) ]
        }

        required init(adaptor: CoreDataAdaptor) {
            self.adaptor = adaptor
        }
    
        func mapping(tomap: inout Employee, context: MappingContext) {
            // Company must be transformed into something Core Data can use in this case.
            let companyMapping = CompanyTransformableMapping()
            
            // No need to map the primary key here.
            tomap.employer              <- .mapping("company", companyMapping) >*<
            tomap.name                  <- "data.name" >*<
            context
        }
    }
    ```
    Without storage
    ```swift
    class CompanyMapping: AnyMapping {
        // associatedtype MappedObject = Company is inferred by `tomap`
    
        func mapping(tomap: inout Company, context: MappingContext) {
            let employeeMapping = EmployeeMapping(adaptor: CoreDataAdaptor())
        
            tomap.employees             <- .mapping("employees", employeeMapping) >*<
            tomap.founder               <- .mapping("founder", employeeMapping) >*<
            tomap.uuid                  <- "uuid" >*<
            tomap.name                  <- "name" >*<
            tomap.foundingDate          <- "data.founding_date"  >*<
            tomap.pendingLawsuits       <- "data.lawsuits.pending"  >*<
            context
        }
    }
    ```

2. Create your Crust Mapper.
    ```swift
    let mapper = Mapper()
    ```

3. Use the mapper to convert to and from `JSONValue` objects
    ```swift
    let json = try! JSONValue(object: [
                "uuid" : "uuid123",
                "name" : "name",
                "employees" : [
                    [ "name" : "Fred", "uuid" : "ABC123" ],
                    [ "name" : "Wilma", "uuid" : "XYZ098" ]
                ]
                "founder" : NSNull(),
                "data" : [
                    "lawsuits" : [
                        "pending" : 5
                    ]
                ],
                "data.founding_date" : NSDate().toISOString(),
            ]
    )

    let company: Company = try! mapper.map(from: json, using: CompanyMapping())

    // Or if json is an array.
    let company: [Company] = try! mapper.map(from: json, using: CompanyMapping())
    ```

NOTE:
`JSONValue` can be converted back to an `AnyObject` variation of json via `json.values()` and to `NSData` via `try! json.encode()`.

###Nested Mappings
Crust supports nested mappings for nested models
E.g. from above
```swift
func mapping(inout tomap: Company, context: MappingContext) {
    let employeeMapping = EmployeeMapping(adaptor: CoreDataAdaptor())
    
    tomap.employees <- Binding.mapping("employees", employeeMapping) >*<
    context
}
```

###Binding and collections

`Binding` provides specialized directives when mapping collections. Use the `.collectionMapping` case to inform the mapper of these directives. They include
* replace and/or delete objects
* append objects to the collection
* unique objects in collection (remove duplicates)
By default using `.mapping` will `(insert: .append, unique: true)`.

```swift
public enum CollectionInsertionMethod<Container: Sequence> {
    case append
    case replace(delete: ((_ orphansToDelete: Container) -> Container)?)
}<commit_msg>[v0.2] newline<commit_after>[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/Crust.svg)](https://img.shields.io/cocoapods/v/Crust.svg)
[![Build Status](https://travis-ci.org/rexmas/Crust.svg)](https://travis-ci.org/rexmas/Crust)

#Crust
A flexible Swift framework for converting classes and structs to and from JSON with support for storage solutions such as Realm.

#Features 🎸
- [Structs and Classes](#structs-and-classes)
- [Separation of Concerns (Mapped Model, Mapping, Storage)](#separation-of-concerns)
- [Type safe JSON](#jsonvalue-for-type-safe-json)
- [How To Map](#how-to-map)
  - [Nested Mappings](#nested-mappings)
  - [Mapping Context](#mapping-context)
  - [Custom Transformations](#custom-transformations)
  - [Different Mappings for Same Model](#different-mappings-for-same-model)
- [Storage Adaptor](#storage-adaptor)
- [Realm](#realm)
- Supports Optional Types and Collections.

#Requirements
iOS 8.0+
Swift 3.0+

#Installation
### CocoaPods
```
platform :ios, '8.0'
use_frameworks!

pod 'Crust'
```

#Structs and Classes
Can map to/from classes or structs
```swift
class Company {
    var employees = Array<Employee>()
    var uuid: String = ""
    var name: String = ""
    var foundingDate: NSDate = NSDate()
    var founder: Employee?
    var pendingLawsuits: Int = 0
}
```
If you have no need for storage, which will generaly be the case for structs, use `AnyMappable`.
```swift
struct Person: AnyMappable {
    var bankAccounts: Array<Int> = [ 1234, 5678 ]
    var attitude: String = "awesome"
    var hairColor: HairColor = .Unknown
    var ownsCat: Bool? = nil
}
```

#Separation of Concerns

By design Crust is built with [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) in mind. It makes no assumptions about how many ways a user would like to map to and from JSON and how many various ways the user would like to store their models.

Crust has 2 basic protocols:
- `Mapping`
	- How to map JSON to and from a particular model - (model is set by the `associatedtype MappedObject` if mapping to an sequence of objects set `associatedtype SequenceKind`).
	- May include primary key(s) and nested mapping(s).
- `Adaptor`
	- How to store and retrieve model objects used for mapping from a backing store (e.g. Core Data, Realm, etc.).

And 2 additional protocols when no storage `Adaptor` is required:
- `AnyMappable`
	- Inherited by the model (class or struct) to be mapped to and from JSON.
- `AnyMapping`
	- A `Mapping` that does not require an `Adaptor`.

There are no limitations on the number of various `Mapping`s and `Adaptor`s one may create per model for different use cases.

#JSONValue for type safe JSON
Crust relies on [JSONValue](https://github.com/rexmas/JSONValue) for it's JSON encoding and decoding mechanism. It offers many benefits including type safety, subscripting, and extensibility through protocols.

#How To Map

1. Create your mappings for your model using `Mapping` if with storage or `AnyMapping` if without storage.

    With storage (assume `CoreDataAdaptor` conforms to `Adaptor`)
    ```swift
    class EmployeeMapping: Mapping {
    
        var adaptor: CoreDataAdaptor
        var primaryKeys: [PrimaryKeyDescriptor]? {
            // property == attribute on the model, keyPath == keypath in the JSON blob, transform == tranform to apply to data from JSON blob.
            return [ (property: "uuid", keyPath: "data.uuid", transform: nil) ]
        }

        required init(adaptor: CoreDataAdaptor) {
            self.adaptor = adaptor
        }
    
        func mapping(tomap: inout Employee, context: MappingContext) {
            // Company must be transformed into something Core Data can use in this case.
            let companyMapping = CompanyTransformableMapping()
            
            // No need to map the primary key here.
            tomap.employer              <- .mapping("company", companyMapping) >*<
            tomap.name                  <- "data.name" >*<
            context
        }
    }
    ```
    Without storage
    ```swift
    class CompanyMapping: AnyMapping {
        // associatedtype MappedObject = Company is inferred by `tomap`
    
        func mapping(tomap: inout Company, context: MappingContext) {
            let employeeMapping = EmployeeMapping(adaptor: CoreDataAdaptor())
        
            tomap.employees             <- .mapping("employees", employeeMapping) >*<
            tomap.founder               <- .mapping("founder", employeeMapping) >*<
            tomap.uuid                  <- "uuid" >*<
            tomap.name                  <- "name" >*<
            tomap.foundingDate          <- "data.founding_date"  >*<
            tomap.pendingLawsuits       <- "data.lawsuits.pending"  >*<
            context
        }
    }
    ```

2. Create your Crust Mapper.
    ```swift
    let mapper = Mapper()
    ```

3. Use the mapper to convert to and from `JSONValue` objects
    ```swift
    let json = try! JSONValue(object: [
                "uuid" : "uuid123",
                "name" : "name",
                "employees" : [
                    [ "name" : "Fred", "uuid" : "ABC123" ],
                    [ "name" : "Wilma", "uuid" : "XYZ098" ]
                ]
                "founder" : NSNull(),
                "data" : [
                    "lawsuits" : [
                        "pending" : 5
                    ]
                ],
                "data.founding_date" : NSDate().toISOString(),
            ]
    )

    let company: Company = try! mapper.map(from: json, using: CompanyMapping())

    // Or if json is an array.
    let company: [Company] = try! mapper.map(from: json, using: CompanyMapping())
    ```

NOTE:
`JSONValue` can be converted back to an `AnyObject` variation of json via `json.values()` and to `NSData` via `try! json.encode()`.

###Nested Mappings
Crust supports nested mappings for nested models
E.g. from above
```swift
func mapping(inout tomap: Company, context: MappingContext) {
    let employeeMapping = EmployeeMapping(adaptor: CoreDataAdaptor())
    
    tomap.employees <- Binding.mapping("employees", employeeMapping) >*<
    context
}
```

###Binding and collections

`Binding` provides specialized directives when mapping collections. Use the `.collectionMapping` case to inform the mapper of these directives. They include
* replace and/or delete objects
* append objects to the collection
* unique objects in collection (remove duplicates)

By default using `.mapping` will `(insert: .append, unique: true)`.

```swift
public enum CollectionInsertionMethod<Container: Sequence> {
    case append
    case replace(delete: ((_ orphansToDelete: Container) -> Container)?)
}
{'fraction_non_alphanumeric': 0.10874439461883408, 'fraction_numerical': 0.004633781763826607, 'mean_word_length': 2.712819089900111, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 29, 'https://': 12, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1577982', 'n_tokens_mistral': 4073, 'n_tokens_neox': 3778, 'n_words': 1303}