Property mapping for Objective-C iOS apps.

Related tags

JSON PropertyMapper
Overview

CI Status codecov Version License Platform

Stop repeating your data parsing code in iOS apps.

Data parsing is one of most common tasks we need to do in our apps, yet still majority of people do this parsing by hand, always repeating the same code for each class they need to map.

Usual parsing requires this steps:

  • make sure you translate NSNull to nil and not crash
  • gracefully handle optional params
  • do type conversions
  • data format validation

Why Property Mapper?

There are libraries helping with that like Mantle, RESTKit and many more… But I wanted something that's self contained, easy to change / remove and requires minimal amount of code.

I've created Property Mapper as part of working on Foldify, a simple self contained solution that allows you to specify mapping between data you receive and data representation you have in your application... with some additional features, like type boxing, validation.

I don't like passing around JSON so I write parsing on top of native objects like NSDictionary/NSArray. If you get data as JSON just write a simple category that transforms JSON to native objects using NSJSONSerialization.

Example usage

Let's assume you have object like this:

@interface TestObject : NSObject
@property(nonatomic, strong) NSURL *contentURL;
@property(nonatomic, strong) NSURL *videoURL;
@property(nonatomic, strong) NSNumber *type;
@property(nonatomic, strong) NSString *title;
@property(nonatomic, strong) NSString *uniqueID;

@end

and you receive data from server in this format:

@{
  @"videoURL" : @"http://test.com/video.mp4",
	@"name" : @"Some Cool Video",
	@"videoType" : [NSNull null],
	@"sub_object" : @{
			@"title" : @616,
			@"arbitraryData" : @"data"
	}
}

this is the code you would write in your parsing code:

[KZPropertyMapper mapValuesFrom:dictionary toInstance:self usingMapping:@{
   @"videoURL" : KZBox(URL, contentURL),
     @"name" : KZProperty(title),
     @"videoType" : KZProperty(type),
     @"sub_object" : @{
         @"title" : KZProperty(uniqueID)
         }

  }];

Quite obvious what it does but in case you are confused, it will translate videoURL string to contentURL NSURL object, it will also grab title from sub_object and assign it to uniqueID. It also handles NSNull.

Advanced usage

Let's now change our mind and decide that we want our type property to be typedef enumeration, it's quite easy with KZPropertyMapper, change type mapping to following and add following method:

@"videoType" : KZCall(videoTypeFromString:, type),

//! implemented on instance you are parsing
- (id)videoTypeFromString:(NSString *)type
{
  if ([type isEqualToString:@"shortVideo"]) {
    return @(VideoTypeShort);
  }

  return @(VideoTypeLong);
}

If you need property name being passed while using selectors, just pass two-argument selector in KZCall. Second argument will be property name passed as NSString.

@"video" : KZCall(objectFromDictionary:forPropertyName:, type),

//! implemented on instance you are parsing
- (id)objectFromDictionary:(NSDictionary *)dictionary forPropertyName:(NSString *)propertyName
{
  Class objectClass = [self classForProperty:propertyName];
  id object = [objectClass new];
  [object updateFromDictionary:dictionary];
  return object;
}

Done. KVC should also take care of escaping NSNumber into int if your property uses primitive type. Same approach will work for sub-object instances or anything that you can assign to property.

Validations

You can also validate your server data before mapping it:

[KZPropertyMapper mapValuesFrom:dictionary toInstance:self usingMapping:@{
    @"videoURL" : KZBox(URL, contentURL).isRequired().min(10),
    @"name" : KZProperty(title).lengthRange(5, 12),
    @"videoType" : KZProperty(type),
    @"sub_object" : @{
      @"title" : KZProperty(uniqueID),
    },
  }];

Validators can be chained together, you can specify as many as you wish for each field, validation happens on source data before mapping happens.

If validation fails mapValues will return NO as a result, and you can use expanded method to get list of validation errors.

Any validation errors will prevent mapping, as data might be corrupted and we don't want partially updated data.

Built-in validations

Strings

  • isRequired
  • matchesRegEx
  • length
  • minLength
  • maxLength
  • lengthRange
  • oneOf
  • equalTo

Numbers

  • min
  • max
  • range

If you want more you can add validations as categories on KZPropertyDescriptor, check sample code to see how it's done, it's extremely simple.

Referencing arrays items

If your data comes to you in ordered array instead of dictionaries you can reference that as well:

sourceData = @{@"sub_object_array" : @[@"test", @123]}

@{@"sub_object_array" : @{@1 : KZProperty(uniqueID)}

This will grab first item from sub_object_array and assign it to uniqueID. It also works recursively.

Expanding boxing capabilities

You can expand boxing capabilities across whole application easily, just add category on KZPropertyMapper that implements methods like this:

+ (id)boxValueAsType:(id)value
{
	//! your boxing
}

Now you can use @Type mapping everywhere in your code.

Changelog

2.5.0

  • Added KZList that allows you to map single source property into multiple target ones.
  • Added type checking while parsing, if mismatched type is found a property will be ignored and a warning will be generated.
  • Fixed few minor bugs when working with unexpected server responses.
@{
  	@"videoURL" : KZList(
				KZBoxT(object, URL, contentURL),
				KZPropertyT(object, uniqueID),
				KZCallT(object, passthroughMethod:, type)
			)
}

Contributed by Marek Cirkos

2.0

  • New property syntax (old one still works) that allows you to get compile errors when you misspell property names.
  • Introduced concept of validators, chain-able.

Time to implement this changes was sponsored by The App Business.

Installing

  • Using CocoaPods: Add the following line to your Podfile:

    pod "KZPropertyMapper"

  • Using Carthage: Add the following to your Cartfile:

    github "krzysztofzablocki/KZPropertyMapper" "master"

  • Or just add the KZPropertyMapper/ folder to your project, making sure you enable ARC on these files.

Final note

Unit tests should serve as documentation. Default boxing types include @URL and @Date.

Follow me on twitter

Comments
  • No mapping if value from custom boxing is nil

    No mapping if value from custom boxing is nil

    If the value from a boxing method (e.g. in a custom category) returns nil no mapping on the instance happens and the old value persists. (see: https://github.com/krzysztofzablocki/KZPropertyMapper/blob/master/KZPropertyMapper/KZPropertyMapper.m#L164)

    Is there any chance to set a property to nil for custom boxed value?

    enhancement 
    opened by fonkadelic 7
  • No testing of validation errors in KZPropertyMapperValidatorTests.m

    No testing of validation errors in KZPropertyMapperValidatorTests.m

    It seems that the method +mapValuesFrom:toInstance:usingMapping:errors: appears only to be tested when passing nil to this parameter. There is no testing of the existence of a returned array of errors and their expected contents/quality.

    opened by BoneDesert 5
  • Assertion failure - which property?

    Assertion failure - which property?

    Hi, this is more of a question.

    From the call stack below, how can I figure out which property causes the problem? Usually it happens when the API returns something I did not account for, but I never know where to start.

    Thanks!

    Cheers Simon

    2013-11-10 05:00:18.005 Passions[17581:70b] **\* Assertion failure in +[KZPropertyMapper validateMapping:withValuesArray:], /Users/simon/Documents/code/ObjC/iOS/Passions/Pods/KZPropertyMapper/KZPropertyMapper/KZPropertyMapper.m:192 2013-11-10 05:00:18.008 Passions[17581:70b] **\* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid condition not satisfying: [key isKindOfClass:NSNumber.class]' **\* First throw call stack: ( 0 CoreFoundation 0x01d5f5e4 **exceptionPreprocess + 180 1 libobjc.A.dylib 0x01ae28b6 objc_exception_throw + 44 2 CoreFoundation 0x01d5f448 +[NSException raise:format:arguments:] + 136 3 Foundation 0x00657fee -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 116 4 Passions 0x0005290b __52+[KZPropertyMapper validateMapping:withValuesArray:]_block_invoke + 363 5 CoreFoundation 0x01de840c __65-[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke + 76 6 CoreFoundation 0x01de832e -[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:] + 238 7 CoreFoundation 0x01d4d525 -[NSDictionary enumerateKeysAndObjectsUsingBlock:] + 53 8 Passions 0x000526dc +[KZPropertyMapper validateMapping:withValuesArray:] + 284 9 Passions 0x00052556 +[KZPropertyMapper validateMapping:withValues:] + 854 10 Passions 0x000530d6 __57+[KZPropertyMapper validateMapping:withValuesDictionary:]_block_invoke + 342 11 CoreFoundation 0x01de840c __65-[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke + 76 12 CoreFoundation 0x01de832e -[__NSDictionaryI enumerateKeysAndObjectsWithOptions:usingBlock:] + 238 13 CoreFoundation 0x01d4d525 -[NSDictionary enumerateKeysAndObjectsUsingBlock:] + 53 14 Passions 0x00052ec6 +[KZPropertyMapper validateMapping:withValuesDictionary:] + 278 15 Passions 0x00052519 +[KZPropertyMapper validateMapping:withValues:] + 793 16 Passions 0x0004f6a6 +[KZPropertyMapper mapValuesFrom:toInstance:usingMapping:errors:] + 182 17 Passions 0x0004f588 +[KZPropertyMapper mapValuesFrom:toInstance:usingMapping:] + 184 18 Passions 0x00055860 -[LFMAlbumInfo initWithJson:] + 2608 19 Passions 0x0005ea32 __57-[LastFmFetchr getInfoForAlbum:byArtist:mbid:completion:]_block_invoke_2 + 146 20 Passions 0x0005f97a -[LastFmFetchr handleRequestSuccess:task:methodParamValue:jsonContentKey:success:failure:] + 1066 21 Passions 0x0005e91b __57-[LastFmFetchr getInfoForAlbum:byArtist:mbid:completion:]_block_invoke + 363 22 Passions 0x000176f7 __55-[AFHTTPSessionManager GET:parameters:success:failure:]_block_invoke + 231 23 Passions 0x0003a3d5 __72-[AFURLSessionManagerTaskDelegate URLSession:task:didCompleteWithError:]_block_invoke_2 + 213 24 libdispatch.dylib 0x0295d7f8 _dispatch_call_block_and_release + 15 25 libdispatch.dylib 0x029724b0 _dispatch_client_callout + 14 26 libdispatch.dylib 0x0296075e _dispatch_main_queue_callback_4CF + 340 27 CoreFoundation 0x01dc4a5e __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE** + 14 28 CoreFoundation 0x01d056bb __CFRunLoopRun + 1963 29 CoreFoundation 0x01d04ac3 CFRunLoopRunSpecific + 467 30 CoreFoundation 0x01d048db CFRunLoopRunInMode + 123 31 GraphicsServices 0x02f629e2 GSEventRunModal + 192 32 GraphicsServices 0x02f62809 GSEventRun + 104 33 UIKit 0x00c75d3b UIApplicationMain + 1225 34 Passions 0x000025ed main + 141 35 libdyld.dylib 0x02c0470d start + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException (lldb)
    opened by simontaen 5
  • Suppressing error messages

    Suppressing error messages

    Is it possible, to somehow disable warnings like: KZPropertyMapper: Ignoring mapping from matchTime to matchTime because type (null) does NOT match NSString

    I'm avare that this might be null, NSString out of null is empty. I'm ready for that, but it just overflows my log and would not like to see it.

    opened by ghost 4
  • Carthage support

    Carthage support

    Hello!

    To start: great library! I went to use this today on my Carthage-driven project and noticed the only supported methods of installation were Cocoapods and drag-and-drop, so I got to work. :)

    This pull request adds an .xcodeproj with a shared iOS and Mac OS X Framework target. I've attempted to get this working with the most minimal changes to the original source/structure so as not to affect others using KZPropertyMapper via other installation methods.

    I've tested this with the latest Xcode 7 build and Carthage version 0.8 (brew upgrade carthage to fetch, if needed).

    All you need to do to get this working in an example project is have a Cartfile with the following contents:

    github "cfdrake/KZPropertyMapper" "master"

    From here, run carthage bootstrap. You'll see there is a .framework file in both Carthage/Build/iOS and Carthage/Build/Mac. You can now follow the instructions on the Carthage README to include the compiled frameworks in an app. After doing this, a simple @import KZPropertyMapper at the top of your source file should include the relevant header files.

    Let me know if this seems to be working/you have any suggestions/you're open to adding support for this dependency manager. Thanks!

    opened by cfdrake 4
  • Support for compound property?

    Support for compound property?

    I was wondering, how would I support compound property, for example, if the values of latitude and longitude are in the root dictionary and I want them to create a CLLocation instance. I couldn't thought of an elegant way to do it though except falling back to hand crafting loading.

    Also, I was wondering if there's support for explicitly ignoring fields.

    opened by lxcid 4
  • Relationship mapping

    Relationship mapping

    To whom it may concern

    I have been using a couple of data mapping libraries and this one seems to be very light weight and easy to use. However one of the thing I would love to have is relationship mapping. Like one-one, one-many relationship

    I.e: With this dictionary @{ @"videoURL" : @"http://test.com/video.mp4", @"name" : @"Some Cool Video", @"videoType" : [NSNull null], @"sub_object" : @{ @"title" : @616, @"arbitraryData" : @"data" } }

    Can I map the sub_object to a class that I already have

    @interface MyClass: NSObject

    @property NSString title; @property NSString arbitraryData

    @end

    Kind regards Leon

    opened by ngoccuong291 4
  • Adds tests for mapping protocol dependencies

    Adds tests for mapping protocol dependencies

    Adds tests for #52.

    When mapping anonymous objects that adhere to some protocol, prefer using id<SomeProtocol> as the @property's type.

    Avoid generic type definitions, such as NSObject<SomeProtocol>, because type-encoding is more difficult to reason about. If a concrete type declaration is publically available, such as a NSObject subclass adhering to SomeProtocol, it is safe to use the exact @class type.

    opened by KevinVitale 3
  • Validates @encodes(id<SomeProtocol>) will pass

    Validates @encodes(id) will pass

    Mapping can fail for valid properties that include only protocol information. For instance, id<MyProtocol> will fail, despite the fact that object conforms to SomeProtocol.

    @encode(id<SomeProtocol>) has undocumented behavior which includes a property's Protocol information, as discussed in this StackOverflow thread.

    In the event of an id<SomeProtocol>, I've used an NSScanner to enumerate each Protocol and test for protocol conformance on object.

    opened by KevinVitale 3
  • Asserts when receiving an NSArray instead of an NSDictionary

    Asserts when receiving an NSArray instead of an NSDictionary

    Hi, I just updated to 2.5 and I'm really excited with the new features :)

    Before the update I was using a modified version in which I surrounded some methods with a try/catch to avoid crashes in the testers devices because of the API changing constantly. Now I was thinking of use the new version to accomplish the same purpose, but I encountered a problem with an assertion.

    JSON I expect:

    {
        ...
        thing: 
            {
                a1 : "hello",
                ...
            },
        ...
    }
    

    Mapping:

        [KZPropertyMapper mapValuesFrom:properties 
                              toInstance:self
                           usingMapping:@{
                                      @"a1" : KZProperty(attribute1),
                                      }
         ];
    

    JSON I'm reciving:

    {
        ...
        thing: [],
        ...
    }
    

    This Asserts on line 252: AssertTrueOrReturn([key isKindOfClass:NSNumber.class]); inside the enumeration block.

    The problem is natural. I'm giving an array to the mapper when it expects a dictionary. The crash is absolutely normal but with the new feature I was hoping that this won't happen anymore. If this is the desired behaviour there is no problem ;)

    opened by alexito4 3
  • Toggle logging of ignored values in class method, not #define

    Toggle logging of ignored values in class method, not #define

    This addresses the issue raised by @andrewsardone in krzysztofzablocki/KZPropertyMapper#10, using his proposed solution.

    Users (CocoaPods users in particular) should now use the class method +[KZPropertyMapper logIgnoredValues:] to configure whether to log ignored values.

    The previous method, via #define KZPropertyMapperLogIgnoredValues, will still work but is no longer exposed in the public interface.

    Credit to @andrewsardone for the idea and for suggesting that the class method be placed in a Debug category, to separate it from the class's main concern (that is, mapping stuff).

    opened by cdzombak 3
  • Better warning message for missing Box custom methods

    Better warning message for missing Box custom methods

    We could modify the Assertion code to handle better cases where the developer forgot to write the code to handle the custom Box.

        SEL mappingSelector = NSSelectorFromString([NSString stringWithFormat:@"boxValueAs%@:", mappingType]);
        AssertTrueOrReturnNoBlock([self respondsToSelector:mappingSelector], ^(NSError *error) {
        });
    

    In the AssertTrueOrReturnNoBlock we could print the expected selector.

    opened by ppaulojr 1
  • Ability to Propagate Errors when using Custom Boxing / Call

    Ability to Propagate Errors when using Custom Boxing / Call

    Lets say I have a JSON object like this:

      { 
           success: false,
           error: {code: 1, message: "Something was wrong!"},
           data: nil
      }
    

    And I wan't to map object like this:

    @interface Response : NSObject
    @property (assign, nonatomic) BOOL    isSuccess;
    @property (strong, nonatomic) id      data;
    @property (strong, nonatomic) NSError *error;
    @end
    

    I can map error with KZCall or by custom boxing. But this methods unable to specify any errors during certain class instantiation or mapping. Is there any way to fix it with current library implementation?

    enhancement 
    opened by lazarev 3
  • KZCall on different object?

    KZCall on different object?

    Let's say I have an User object, and an user belongs to a group.

    @interface User : NSObject
    @property (strong, nonatomic) NSString *firstName;
    @property (strong, nonatomic) NSString *lastName;
    @property (strong, nonatomic) Group *group;
    @end
    
    @interface Group : NSObject
    @property (strong, nonatomic) NSString *name;
    @end
    

    Now, what I'd like to be able to do is something like this:

    @implementation User
    
    - (instancetype)initWithDict:(NSDictionary *)dict {
        self = [super init];
    
        KZPropertyMapper mapValuesFrom:dict toInstance:self usingMapping:@{
            @"name": KZProperty(firstName),
            @"lastname": KZProperty(lastName),
            @"group": KZCall(Group, groupWithDict:, group),
        }];
    
        return self;
    }
    
    @end
    

    So, call [Group groupWithDict:dict] and save that result to self.group. However, I now constantly have to create an intermediary helper method on the object I'm parsing, so the user object now gets something like this:

    - (id)groupWithDict:(NSDictionary *)dict {
        return [Group groupWithDict:dict];
    }
    

    After writing a bunch of classes like this, it gets really tedious to duplicate these methods like this. Is there any solution? It looked like KZCallT would be handy, but the property then needs to be on the target object instead of self.

    enhancement 
    opened by kevinrenskers 4
Releases(2.9)
Owner
Krzysztof Zabłocki
Making Swift engineers more efficient through tools and workflows.  My code powers up over 70 000+ apps.
Krzysztof Zabłocki
Reflection based (Dictionary, CKRecord, NSManagedObject, Realm, JSON and XML) object mapping with extensions for Alamofire and Moya with RxSwift or ReactiveSwift

EVReflection General information At this moment the master branch is tested with Swift 4.2 and 5.0 beta If you want to continue using EVReflection in

Edwin Vermeer 964 Dec 14, 2022
Easy JSON to NSObject mapping using Cocoa's key value coding (KVC)

#Motis Object Mapping Easy JSON to NSObject mapping using Cocoa's key value coding (KVC) Motis is a user-friendly interface with Key Value Coding that

Mobile Jazz 249 Jun 29, 2022
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
This framework implements a strict JSON parser and generator in Objective-C.

SBJson 5 Chunk-based JSON parsing and generation in Objective-C. Overview SBJson's number one feature is stream/chunk-based operation. Feed the parser

null 3.8k Jan 5, 2023
The better way to deal with JSON in Objective-C (inspired by SwiftyJSON)

NSTEasyJSON Inpired by SwiftyJSON. NSTEasyJSON makes it easy to deal with JSON data in Objective-C. Why is the typical JSON handling in Objective-C NO

Timur Bernikovich 11 Apr 2, 2020
Magical Data Modeling Framework for JSON - allows rapid creation of smart data models. You can use it in your iOS, macOS, watchOS and tvOS apps.

JSONModel - Magical Data Modeling Framework for JSON JSONModel allows rapid creation of smart data models. You can use it in your iOS, macOS, watchOS

JSONModel 6.9k Dec 8, 2022
Magical Data Modeling Framework for JSON - allows rapid creation of smart data models. You can use it in your iOS, macOS, watchOS and tvOS apps.

JSONModel - Magical Data Modeling Framework for JSON JSONModel allows rapid creation of smart data models. You can use it in your iOS, macOS, watchOS

JSONModel 6.8k Nov 19, 2021
HandyJSON is a framework written in Swift which to make converting model objects to and from JSON easy on iOS.

HandyJSON To deal with crash on iOS 14 beta4 please try version 5.0.3-beta HandyJSON is a framework written in Swift which to make converting model ob

Alibaba 4.1k Dec 29, 2022
A library to turn dictionary into object and vice versa for iOS. Designed for speed!

WAMapping Developed and Maintained by ipodishima Founder & CTO at Wasappli Inc. Sponsored by Wisembly A fast mapper from JSON to NSObject Fast Simple

null 8 Nov 20, 2022
Pic-To-Send iOS Application Repository

PicToSend_G03_iOS_Project Pic-To-Send iOS Application Repository Application User Interface How to run the application: Pre-requisite: Xcode 13 or lat

JayaShankar Mangina 0 Feb 8, 2022
RSS reader for macOS and iOS.

NetNewsWire It’s a free and open-source feed reader for macOS and iOS. It supports RSS, Atom, JSON Feed, and RSS-in-JSON formats. More info: https://n

Ranchero Software 6.3k Dec 29, 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
Aging FRIENDS App for iOS Using Swift

Aging F.R.I.E.N.D.S ?? So no one told you how old they are ? ?? ?? ?? ?? Ever wondered how old the FRIENDS cast are ? Let's find out ! Reemz.Mac.48452

Reem 1 Dec 11, 2021
An advanced Swift (IOS Native) application that uses SOLID architectural principles, consumes a RESTFUL Service, downloads & images using best practices.

dog-playground-ios An advanced Swift (IOS Native) application that uses SOLID architectural principles, consumes a RESTFUL Service, downloads &amp; im

Amose Suwali 1 Jan 10, 2022
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

Oven Bits 34 Apr 15, 2022
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

MartinStamenkovski 37 Dec 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
An iOS framework for creating JSON-based models. Written in Swift.

An iOS framework for creating JSON-based models. Written in Swift (because it totally rules!) Requirements iOS 8.0+ Xcode 7.3 Swift 2.2 Installation E

Oven Bits 448 Nov 8, 2022
A collection of Swift Property Wrappers (formerly "Property Delegates")

?? ?? Burritos A collection of well tested Swift Property Wrappers. @AtomicWrite @Clamping @Copying @DefaultValue @DynamicUIColor @EnvironmentVariable

Guillermo Muntaner 1.3k Dec 26, 2022
Data Mapping library for Objective C

OCMapper is a data mapping library for Objective C that converts NSDictionary to NSObject

Aryan Ghassemi 346 Dec 8, 2022