A library to turn dictionary into object and vice versa for iOS. Designed for speed!

Related tags

JSON WAMapping
Overview

WAMapping

Version License Platform

Developed and Maintained by ipodishima Founder & CTO at Wasappli Inc.

Sponsored by Wisembly

A fast mapper from JSON to NSObject

  • Fast
  • Simple to write & read
  • Saves you many hours
  • Supports both JSON <-> NSObject
  • Designed for customisation
  • Built-in CoreData, NSCoding and Memory stores
  • Built-in insert or update object
  • Tested

Go visit the wiki for more details about WAMapping advanced use.

WAMapping is a library for iOS to turns dictionaries into objects and objects to dictionary. It's aim is to simplify the boilerplate of manually parsing the data and assigning values to an object. It's even more difficult when it comes to using it with CoreData because of the insert or update. And I do not mention performances involved. WAMapping solves this for you!

Install and use

Cocoapods

Use Cocoapods, this is the easiest way to install the mapper.

pod 'WAMapping'

#import <WAMapping/WAMapping.h>

Setup mapping

On a classical use, the source is known as the response from a server turned into a dictionary and the destination is the destination object to apply the values, for example an NSManagedObject.

Let's assume the Enterprise class as follows:

@interface Enterprise : NSObject

@property (nonatomic, strong) NSNumber *itemID;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSDate *creationDate;
@property (nonatomic, strong) NSNumber *streetNumber;
@property (nonatomic, strong) NSArray *employees; // Can be mutable, or an `NSSet` or an `NSOrderedSet`
@property (nonatomic, strong) NSArray *chiefs;

@end

The itemID ids your object on the store. This is not required but recommended to avoid creating duplicates.

Assuming a json

{
    "id": 1,
    "name": "Wasappli",
    "creation_date": "2013-10-01",
    "address": {
        "street_number": 5149
    }
}

The mapping would looks like

WAEntityMapping *enterpriseMapping = [WAEntityMapping mappingForEntityName:@"Enterprise"];
enterpriseMapping.identificationAttribute = @"itemID";

// Add the classic attributes
[enterpriseMapping addAttributeMappingsFromDictionary:@{
                                                        @"id": @"itemID",
                                                        @"name": @"name",
                                                        @"address.street_number": @"streetNumber"
                                                        }];      
                                                   
// Map custom values. Here an `NSDate` from a string using an `NSDateTransformer`
[enterpriseMapping addMappingFromSourceProperty:@"creation_date"
                          toDestinationProperty:@"creationDate"
                                      withBlock:^id(id value) {
                                          return [dateFormatter dateFromString:value];
                                      }
                                   reverseBlock:^id(id value) {
                                       return [dateFormatter stringFromDate:value];
                                   }];

// Register the mapping for future use
WAMappingRegistrar *registrar = [WAMAppingRegistrar new];
[registrar registerMapping:enterpriseMapping];
// [registrar registerMapping:employeeMapping];
// WAEntityMapping *savedEnterpriseMapping = [registrar mappingForEntityName:@"Enterprise"];

And that's it...!

Use the mapper

First, create a store. This is a required step. I'm providing three stores on this repo:

  • WAMemoryStore which relies on a simple NSMutableSet,
  • WANSCodingStore which saves your objects using NSCoding protocol,
  • WACoreDataStore which makes use of CoreData.

You can easily create your own store is you want to use SQLite for example, go checkout the wiki.

WAMemoryStore *store = [[WAMemoryStore alloc] init];

// or
// WACoreDataStore *store = [[WACoreDataStore alloc] initWithManagedObjectContext:localContext];

// or
// WANSCodingStore *store = [[WANSCodingStore alloc] initWithArchivePath:archivePath];

Then, allocate a mapper with the store

WAMapper *mapper = [[WAMapper alloc] initWithStore:store];

Finally, map the dictionary representation to the object:

[mapper mapFromRepresentation:json
                      mapping:enterpriseMapping
                   completion:^(NSArray *mappedObjects) {
                       firstEnterprise = [mappedObjects firstObject];
                   }];

And voilà!

Add relation ships

WAMapping also supports relationships:

  • classics:
{
    "id": 1,
    "first_name": "Marian",
    "enterprise": {
        "id": 1,
        "name": "Wasappli",
        "creation_date": "2013-10-01",
        "address": {
            "street_number": 5149
        }
    }
}
WARelationshipMapping *enterpriseRelationship = 
[WARelationshipMapping relationshipMappingFromSourceProperty:@"enterprise" toDestinationProperty:@"enterprise" withMapping:enterpriseMapping];
[employeeMapping addRelationshipMapping:enterpriseRelationship];
  • With identification attribute only
{
    "enterprise": {
        "id": 1,
        "name": "Wasappli",
        "creation_date": "2013-10-01",
        "address": {
            "street_number": 5149
        },
        "chiefs": 1 # Could also be [1, 2, 3] 
    },
    "employees": [{
                  "id": 1,
                  "first_name": "Marian"
                  }]
}
WARelationshipMapping *chiefsRelationship = [WARelationshipMapping relationshipMappingFromSourceIdentificationAttribute:@"chiefs" toDestinationProperty:@"chiefs" withMapping:employeeMapping];
[enterpriseMapping addRelationshipMapping:chiefsRelationship];

Reverse mapper

A reverse mapper is also packaged with this library. It supports the reverse transformation from an object to a dictionary.

WAReverseMapper *reverseMapper = [[WAReverseMapper alloc] init];

json = [reverseMapper reverseMapObjects:enterprises
                            fromMapping:enterpriseMapping
                  shouldMapRelationship:nil];

Default mappings

If you have a server which returns all dates within the same format, then you can ask the mapper or the reverse mapper once to transform the value.

Instead of writing

[enterpriseMapping addAttributeMappingsFromDictionary:@{
                                                        @"id": @"itemID",
                                                        @"name": @"name",
                                                        @"address.street_number": @"streetNumber"
                                                        }];      
                                                   
// Map custom values. Here an `NSDate` from a string using an `NSDateTransformer`
[enterpriseMapping addMappingFromSourceProperty:@"creation_date"
                          toDestinationProperty:@"creationDate"
                                      withBlock:^id(id value) {
                                          return [dateFormatter dateFromString:value];
                                      }
                                   reverseBlock:^id(id value) {
                                       return [dateFormatter stringFromDate:value];
                                   }];

You would write

[enterpriseMapping addAttributeMappingsFromDictionary:@{
                                                        @"id": @"itemID",
                                                        @"name": @"name",
                                                        @"address.street_number": @"streetNumber",
                                                        @"creation_date": @"creationDate"
                                                        }];


id(^toDateMappingBlock)(id ) = ^id(id value) {
    if ([value isKindOfClass:[NSString class]]) {
        return [dateFormatter dateFromString:value];
    }
    
    return value;
};

[mapper addDefaultMappingBlock:toDateMappingBlock
           forDestinationClass:[NSDate class]];

The same thing happens to the reverse mapper. Note that if you provide a custom mapping on an NSDate object for a specific property (like a date with only the year), you can add the property to the entity mapping which will override the default behavior for this specific property.

Progress and cancellation

Both WAMapper and WAReverseMapper support NSProgress. Note that Apple explicitely says in their documentation about NSProgressReporting (which we are mimicing here) Objects that adopt this protocol should typically be "one-shot" which means you should use one WAMapper per map operation.

Progress

You can track the progress using this little piece of code. Note that the progress counts the main top objects mapped (if your array contains one object with a thousand objects as relationship, the progress will not reflect the thousand subobjects mapped). This is per choice because adopting child progress prior to iOS 9 is not great.

[mapper.progress addObserver:self
                  forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                     options:NSKeyValueObservingOptionNew
                     context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(fractionCompleted))] && [object isKindOfClass:[NSProgress class]]) {
        NSLog(@"Mapping progress = %f", [change[@"new"] doubleValue]);
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

Cancellation

You can cancel the mapping or the reverse mapping using this piece of code. Note that for cancellation to happen, you have to call the mapping from an other thread!

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    [mapper mapFromRepresentation:JSON mapping:employeeMapping completion:^(NSArray *mappedObjects, NSError *error) {
        NSLog(@"Mapped objects %@ - Error %@", mappedObjects, error);
    }];
});

[mapper.progress cancel];

Side notes

TODOs

  • Benchmark against popular mappers
  • Add more tests for keypath handling, especially on relationship

Inspiration

You'll find inspiration from Restkit and FastEasyMapping. These are both libraries I used on projects but with issues

#Contributing : Problems, Suggestions, Pull Requests?

Please open a new Issue here if you run into a problem specific to WAAppRouting.

For new features pull requests are encouraged and greatly appreciated! Please try to maintain consistency with the existing code style. If you're considering taking on significant changes or additions to the project, please ask me before by opening a new Issue to have a chance for a merge.

#That's all folks !

  • If your are happy don't hesitate to send me a tweet @ipodishima!
  • Distributed under MIT licence.
  • Follow Wasappli on facebook
You might also like...
Argo is a library that lets you extract models from JSON or similar structures in a way that's concise, type-safe, and easy to extend
Argo is a library that lets you extract models from JSON or similar structures in a way that's concise, type-safe, and easy to extend

Argo is a library that lets you extract models from JSON or similar structures in a way that's concise, type-safe, and easy to extend. Using Argo

A library of Swift extensions to turbocharge your iOS development.
A library of Swift extensions to turbocharge your iOS development.

Alexandria A library of Swift extensions to turbocharge your iOS development. Background Here at Oven Bits, we love Swift. We started using it when it

Library Genesis iOS Client
Library Genesis iOS Client

LibraryGenesis This application is showing books from Library Genesis. If you found this project useful leave a star ⭐️ Features Search books with tit

[Deprecated] A shiny JSON parsing library in Swift :sparkles: Loved by many from 2015-2021
[Deprecated] A shiny JSON parsing library in Swift :sparkles: Loved by many from 2015-2021

🚨 Deprecation Notice 🚨 Gloss has been deprecated in favor of Swift's Codable framework. The existing Gloss source is not going away, however updates

Himotoki (紐解き) is a type-safe JSON decoding library written purely in Swift.

Himotoki Himotoki (紐解き) is a type-safe JSON decoding library written purely in Swift. This library is highly inspired by the popular Swift JSON parsin

Library of Swiftui Views conforming to Codable, meaning we can produce JSON driven UI!

CodableView Library of Swiftui Views conforming to Codable, meaning we can produce JSON driven UI! Adding a CodableView Type Create View that conforms

Swift library for JSON-RPC

JSONRPC There are already a bunch of packages out there for doing JSON-RPC in Swift. This one is just very simple and makes no assumptions about the t

A type-safe JSON-RPC 2.0 library purely written in Swift

JSONRPCKit JSONRPCKit is a type-safe JSON-RPC 2.0 library purely written in Swift. // Generating request JSON let batchFactory = BatchFactory(version:

A JSON deserialization library for Swift

Mapper Mapper is a simple Swift library to convert JSON to strongly typed objects. One advantage to Mapper over some other libraries is you can have i

Releases(0.0.8)
  • 0.0.8(Aug 30, 2017)

  • 0.0.7(Apr 22, 2016)

  • 0.0.6(Mar 21, 2016)

    Fixed issues with relation ships when relation ship objects also have relation ship. When you are mapping one object type from representation, you only get the array of those objects on first hierarchy level. Previously, you'd get every objects mapped in the process.

    Source code(tar.gz)
    Source code(zip)
  • 0.0.5(Mar 18, 2016)

  • 0.0.4(Mar 17, 2016)

    • Added NSProgress support

    You can track progress using

    [mapper.progress addObserver:self
                      forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                         options:NSKeyValueObservingOptionNew
                         context:NULL];
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(fractionCompleted))] && [object isKindOfClass:[NSProgress class]]) {
            NSLog(@"Mapping progress = %f", [change[@"new"] doubleValue]);
        } else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
    

    It also supports cancellation

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        [mapper mapFromRepresentation:JSON mapping:employeeMapping completion:^(NSArray *mappedObjects, NSError *error) {
            NSLog(@"Mapped objects %@ - Error %@", mappedObjects, error);
        }];
    });
    
    [mapper.progress cancel];
    
    • Added a basic sample on view controller
    Source code(tar.gz)
    Source code(zip)
  • 0.0.3(Feb 29, 2016)

    • Added WANSCodingStore to the main header
    • Added a way to register default mapping block for specific classes. For example, you can now add a default mapping block to turn strings to NSDate
    id(^toDateMappingBlock)(id ) = ^id(id value) {
        if ([value isKindOfClass:[NSString class]]) {
            return [defaultDateFormatter dateFromString:value];
        }
    
        return value;
    };
    
    [mapper addDefaultMappingBlock:toDateMappingBlock
               forDestinationClass:[NSDate class]];
    
    Source code(tar.gz)
    Source code(zip)
  • 0.0.2(Feb 26, 2016)

Owner
null
JSONHelper - ✌ Convert anything into anything in one operation; JSON data into class instances, hex strings into UIColor/NSColor, y/n strings to booleans, arrays and dictionaries of these; anything you can make sense of!

JSONHelper Convert anything into anything in one operation; hex strings into UIColor/NSColor, JSON strings into class instances, y/n strings to boolea

Baris Sencan 788 Jul 19, 2022
Quickly search the built-in iOS dictionary to see definitions of words. Collect words you want to remember.

Kotoba Quickly search the built-in iOS dictionary to see definitions of words. Collect words you want to remember. Installation (iPhone or iPad) Note:

Will Hains 452 Dec 26, 2022
Decodable Simple and strict, yet powerful object mapping made possible by Swift 2's error handling.

Decodable Simple and strict, yet powerful object mapping made possible by Swift 2's error handling. Greatly inspired by Argo, but without a bizillion

Johannes Lund 1k Jul 15, 2022
Dogtector: dog breed detection app for iOS using YOLOv5 model combined with Metal based object decoder optimized

Dogtector Project description Dogtector is dog breed detection app for iOS using YOLOv5 model combined with Metal based object decoder optimized for u

Bartłomiej Pluta 21 Aug 1, 2022
JSON object with Swift

JSON JSON using @dynamicMemberLookup, which allows us to write more natural code

Jewelz Hu 0 Dec 17, 2021
Simple JSON Object mapping written in Swift

ObjectMapper ObjectMapper is a framework written in Swift that makes it easy for you to convert your model objects (classes and structs) to and from J

Tristan Himmelman 9k Jan 2, 2023
Encode and decode deeply-nested data into flat Swift objects

DeepCodable: Encode and decode deeply-nested data into flat Swift objects Have you ever gotten a response from an API that looked like this and wanted

Mike Lewis 91 Dec 26, 2022
AlamofireObjectMappe - An Alamofire extension which converts JSON response data into swift objects using ObjectMapper

AlamofireObjectMapper An extension to Alamofire which automatically converts JSON response data into swift objects using ObjectMapper. Usage Given a U

Tristan Himmelman 2.6k Dec 29, 2022
An extension for Alamofire that converts JSON data into Decodable objects.

Swift 4 introduces a new Codable protocol that lets you serialize and deserialize custom data types without writing any special code and without havin

Nikita Ermolenko 749 Dec 5, 2022
Codable code is a Swift Package that allows you to convert JSON Strings into Swift structs

Codable code is a Swift Package that allows you to convert JSON Strings into Swift structs.

Julio Cesar Guzman Villanueva 2 Oct 6, 2022