EasyMapping, или Путешествие по JSON'у

Computer programs are the most complex things that humans make. It is also the nature of software to be extensively modified over its productive life. If we can read and understand it, then we can hope to modify and improve it.

Douglas Crockford, автор спецификации JSON


JSON — это мост между двумя мирами: миром веб-сервисов и миром клиентских приложений. Однако мост не настолько совершенен, чтобы данные существовали в одном формате. Пока что мы всегда вынуждены преобразовывать информацию в представление того языка, с которым работаем, для архитектуры того приложения, которое пишем. Для того чтобы такое преобразование было успешным, оно должно быть в первую очередь простым.

Есть много способов превращения JSON в Objective-C обьекты, однако многие из них имеют свои недостатки, которые мешают с ними работать. Есть известный и любимый многими RestKit, однако он, к сожалению, эффективно работает только при наличии идеального REST API. Шаг в сторону — и вы будете забивать гвозди микроскопом, не понимая, зачем нужно писать такие сложные конструкции для достаточно простых вещей. Есть решение от разработчиков GitHub — Mantle, однако с ним вы будете вынуждены наследоваться от базового класса Mantle и постоянно использовать NSValueTransformer — не самую популярную технологию в iOS/Mac OS разработке.

Я хочу рассказать о фреймворке, который недавно нашелся на просторах GitHub, и который позволяет достаточно просто и красиво преобразовывать JSON в Objective-C обьекты — EasyMapping.

Если заинтересовались, добро пожаловать под кат!

Задача


Возьмем для примера следующий JSON:

{
    "name": "Lucas",
    "email": "notexisting@gmail.com",
    "gender" : "male",
    "car": {
        "model": "i30",
        "year": "2013"
    },
    "phones": [
        {
            "ddi": "55",
            "ddd": "85",
            "number": "1111-1111"
        },
        {
            "ddi": "55",
            "ddd": "11",
            "number": "2222-222"
        }
    ]
}


В Objective-C будем использовать такие классы:

typedef enum {
    GenderMale,
    GenderFemale
} Gender;

@interface Person : NSObject

@property (nonatomic, copy)   NSString *name;
@property (nonatomic, copy)   NSString *email;
@property (nonatomic, assign) Gender gender;
@property (nonatomic, strong) Car *car;
@property (nonatomic, strong) NSArray *phones;

@end

@interface Car : NSObject

@property (nonatomic, copy) NSString *model;
@property (nonatomic, copy) NSString *year;

@end

@interface Phone : NSObject

@property (nonatomic, copy) NSString *DDI;
@property (nonatomic, copy) NSString *DDD;
@property (nonatomic, copy) NSString *number;

@end


Реализация


Для каждого класса необходимо создать маппинг (EKObjectMapping), например:

EKObjectMapping * mapping = [[EKObjectMapping alloc] initWithObjectClass:[Person class]];


После чего необходимо добавить соотношения ключ-значение для связи <ключ в JSON> -> <имя свойства обьекта>. Рассмотрим наиболее часто встречающиеся ситуации при маппинге и обработку этих ситуаций при помощи EasyMapping.

1. Ключи в JSON совпадают с именами свойств обьекта

Пример, класс Person:

[mapping mapFieldsFromArray:@[@"name",@"email"]];


2. Ключи в JSON отличаются от имен свойств обьекта

Пример, класс Phone:

[mapping mapFieldsFromDictionary:@{@"ddi":@"DDI", @"ddd":@"DDD"}];


3. Значение требует дополнительной обработки

Пример, класс Person

NSDictionary *genders = @{ @"male": @(GenderMale), @"female": @(GenderFemale) };
[mapping mapKey:@"gender" toField:@"gender" withValueBlock:^(NSString *key, id value) {
    return genders[value];
}]; 


4. Обьект содержит обьект другого класса

Пример, класс Person

[mapping hasOneMapping:[Car objectMapping] forKey:@"car"];


5. Обьект содержит массив обьектов другого класса

Пример. Класс Person

[mapping hasManyMapping:[Phone objectMapping] forKey:@"phones"];


Результат


Полностью маппинги для наших классов:


// Car mapping
+ (EKObjectMapping *)objectMapping
{
	EKObjectMapping * mapping = [[EKObjectMapping alloc] initWithObjectClass:[Car class]];

    [mapping mapFieldsFromArray:@[@"model", @"year"]];

    return mapping;
}

// Phone mapping
+ (EKObjectMapping *)objectMapping
{
	EKObjectMapping * mapping = [[EKObjectMapping alloc] initWithObjectClass:[Phone class]];
    [mapping mapFieldsFromArray:@[@"number"]];
    [mapping mapFieldsFromDictionary:@{
        @"ddi" : @"DDI",
        @"ddd" : @"DDD"
     }];
    return mapping;
}

// Person mapping
+ (EKObjectMapping *)objectMapping
{
	EKObjectMapping * mapping = [[EKObjectMapping alloc] initWithObjectClass:[Person class]];

    NSDictionary *genders = @{ @"male": @(GenderMale), @"female": @(GenderFemale) };
    [mapping mapFieldsFromArray:@[@"name", @"email"]];
    [mapping mapKey:@"gender" toField:@"gender" withValueBlock:^(NSString *key, id value) {
        return genders[value];
    }];
    [mapping hasOneMapping:[Car objectMapping] forKey:@"car"];
    [mapping hasManyMapping:[Phone objectMapping] forKey:@"phones"];

    return mapping;
}


Теперь, когда маппинг готов, создание обьекта Person выглядит следующим образом:

NSDictionary * personProperties = ...; // JSON обьекта Person, приведенный выше, распарсенный в словарь.
Person * person = [[Person alloc] init];
[EKMapper fillObject:person fromExternalRepresentation:personProperties withMapping:mapping];


Таким образом, при создании обьекта не используются никакие конструкции if-else, отсутствуют проверки на NSNull. Кроме того, существующие обьекты можно легко обновить новыми данными, если такая потребность возникнет.

Дополнительные плюшки EasyMapping

1. Поддержка CoreData
2. Встроенная проверка на NSNull — NSNull автоматически заменяется на nil.
3. Возможность сериализации обьектов в NSDictionary/NSArray
4. Поддержка установки через CocoaPods
5. Полностью покрыт тестами.

Удачные практики при реализации

Заполнение обьекта при помощи EKMapper лучше вынести в базовый класс, скажем, BaseModel, следующим образом:

- (id)initWithProperties:(NSDictionary *)properties
{
    if (self = [super init])
    {
        [EKMapper fillObject:self fromExternalRepresentation:properties
                 withMapping:[[self class] objectMapping];
    }
    return self;
}

+ (EKObjectMapping *)objectMapping
{
    //Do nothing. Implement in subclass if you want to initialize object
    //via object mapping
    return nil;
}


Таким образом, создание обьекта будет выглядеть так:

Person * person = [[Person alloc] initWithProperties:personProperties];


Заключение


Каждый качественный open-source продукт — это трамплин, который позволяет нам как программистам прыгать все выше и выше. Проекту EasyMapping всего лишь один месяц, если судить по первому коммиту, однако, на мой взгляд, он уже может соперничать с намного более старыми и продвинутыми решениями, по крайней мере, своей простотой. Хочется пожелать удачи автору этого интересного фреймворка, а всем, кто дочитал до конца, — спасибо за ваше время и удачи в путешествиях по JSON'у!

Ссылки


1. EasyMapping на github
2. CocoaPods

Альтернативные решения

1. RestKit
2. Mantle
  • +17
  • 16.9k
  • 5
Share post

Similar posts

Comments 5

    +1
    Интересно, возможно к следующему проекту вместо RestKit возьму AFNetworking + эту библиотеку или аналог. RestKit, кроме тех недостатков, что в статье перечислены, задолбал постоянной тотальной несовместимостью между версиями.
      0
      Да, ментейнерам рескита уже пора определится, одно и то же действие можно сделать пятью способами, и никогда не известно, какое из них они выпилят в следующей версии.
      0
      А меня радует доведенный до ума MKFoundation.
        0
        Посмотрел MKFoundation, но не увидел там никаких способов маппинга. KVC это хорошо, но не всегда поля JSON будут совпадать с именами свойств обьекта. Я не хочу сказать, что это плохое решение, для разных задач — разные фреймворки. Может для Parse использовать MKFoundation вполне удобно.

        Ранее я тоже использовал нечто подобное, просто в методе initWithProperties брал все ключи JSON и вызывал setValueForKey на создаваемом обьекте. Из минусов — половина маппинга была явной, половина — скрытой, поскольку имена совпадали. Читаемость кода, к сожалению, страдала. И возникали проблемы, когда нужно было взять ключ из словаря словаря, например — начинались костыли. EasyMapping легко решил эту проблему, потому что вместо маппинга для ключа он позволяет маппить keyPath.
          0
          Все верно.
          MKFoundation удобен при работе с Parse SDK.
          Если работать с чистым REST API, то EasyMapping безоговорочно лидирует.

      Only users with full accounts can post comments. Log in, please.