Или сказ о том, как разработка JSON валидатора превратилась в очередной JSON binding
Пока нормальные разработчики пишут приложения я изобретаю велосипеды.
Наверное многие разработчики сталкивались с ситуацией, когда мобильное приложение разрабатывается параллельно с бэкэндом. При этом частенько структуры данных, приходящие в ответ на запрос с сервера, могут меняться. Например, на стороне бэкэнда решат поменять именование одного из ключиков JSON-а, забыв предупредить о этом мобильную команду. Я уже не говорю про ситуации, когда CamelCase нотацию «внезапно» решили поменять на underscore или наоборот. Вы можете сказать, что налицо плохая организация процесса и недостаток общения команд, и будете абсолютно правы. Но когда приложение на смартфоне заказчика перестает работать на демо все взгляды устремляются на мобильщиков.
Одним из выходов из положения является валидация присланного JSON'а в соответствии с JSON-схемой (обзорная статья о JSON схемах).
Например, если присланный нам JSON
{
"numberKey" : 100500,
"arrayKey" : [
{
"number" : 1 ,
"string" : "1"
},
{
"number" : 2 ,
"string" : "2"
},
{
"number" : 3 ,
"string" : "3"
}
]
}
Минимальной JSON схемой, его описывающей будет
{
"type" : "object",
"properties" : {
"numberKey" : {
"type" : "number"
},
"arrayKey" : {
"type" : "array",
"items" : {
"type" : "object",
"properties" : {
"number" : {
"type" : "number"
},
"string" : {
"type" : "string"
}
}
}
}
}
}
Все, что нам в идеале нужно, это взять присланный JSON и провалидировать его с помощью схемы. Казалось-бы, проблема решена. JSON-schema — это стандартный способ описания JSON'а и валидаторы есть под многие языки программирования. Но поиски валидатора для языка Objective-C никакого вменяемого результата не дали, поэтому было решено пойти по пути велосипедостроения.
В итоге на свет появилось поделие под названием JsonSchemaValidator (bitbucket). Для тех, кто захочет попробовать:
pod 'SVJsonSchemaValidator'
Так, с помощью этой библиотечки мы можем составить схему:
id schema = [[SVType object] properties:@{
@"numberKey":[SVType number],
@"arrayKey":[[SVType array] items:[[SVType object] properties:@{
@"number" : [SVType number],
@"string" : [SVType string]
}]]
}];
Как видите, схема почти один к одному повторяет сам JSON. Теперь провалидируем его:
NSError* error = nil;
id validatedJson = [schema validateJson:json error:&error];
Здесь переменные:
json — это распаршенный любым парсером JSON (например, с помощью NSJSONSerialization).
validatedJson — только те объекты, которые прошли валидацию.
error — более или менее вменяемое описание того, какие объекты валидацию не прошли. nil — если все OK.
Предположим, что у вас уже есть класс модели
@interface MyModelObject : NSObject
@property (strong, nonatomic) NSString* string;
@property (strong, nonatomic) NSNumber* number;
@end
И делать двойную работу, и описывать в схеме уже существующие классы не хочется. Можно создать схему прямо из этого класса:
id schema = [[SVType object] properties:@{
@"numberKey":[SVType number],
@"arrayKey":[[SVType array] items:[MyModelObject jsonSchema]]
}];
С помощью objc-runtime метод -jsonSchema для класса MyModelObject пройдется по всем свойствам, которые можно представить в JSON и сгенерирует точно такую-же схему. С помощью этой же схемы можно инстанциировать обьекты типа MyModelObject и заполнить их свойства соответствующими значениями через KVC:
NSDictionary* instanciated = [schema instantiateValidatedJson:validated];
(lldb) po instanciated
$1 = 0x0755fe70 {
numberKey = 100500,
arrayKey = (
"<MyModelObject: 0x7554a30>",
"<MyModelObject: 0x7554610>",
"<MyModelObject: 0x75543b0>",
"<MyModelObject: 0x75619b0>"
);
}
Вот и все, что реализовано на данный момент.
А теперь вопрос, ради которого этот краткий топик и писался: нужен ли этот велосипед кому нибудь, а если да, то что от него еще требуется? Ответы жду в камментах.
Автор: code_monkey