Библиотека сопоставления объектов из JSON в NSObjects
Я пытаюсь создать парсер /objectMapper, который будет создавать объекты Objective C для JSON, которые я использую из службы REST.
Я немного вдохновил RestKit на то, что мои Entities все держат "список расшифровки", который сообщает картографу, с которым идут ключи JSON, с какими объектами. Вот так:
//ObjectEntity implementation
+ (NSDictionary*) mapProperties {
/*
localPropertiy - JSONProperty
*/
return @{
@"name": @"name",
@"category": @"category",
@"possible_scopes": @"possibleScopes",
@"possible_descriptions": @"possibleDescriptions",
@"key": @"keys"
};
}
+ (NSDictionary*) mapRelations {
return [NSDictionary dictionary];
}
Я сделал это, потому что мне нравится инкапсуляция этих изменяемых значений в объект, который они ссылаются. Создание Mapper известно как можно меньше.
Преобразователь делает что-то вроде этого:
+ (NSArray*) parseData:(NSData*) jsonData intoObjectsOfType:(Class) objectClass {
//Parser result from web service
NSError *error = nil;
CJSONDeserializer *deserializer = [CJSONDeserializer deserializer];
[deserializer setNullObject:nil];
NSArray *objects = [deserializer deserializeAsArray:jsonData error:&error];
NSMutableArray *result = [NSMutableArray array];
for (NSDictionary *o in objects) {
id <EntityProtocol> entity = [[objectClass alloc] init];
NSDictionary *jsonKeys = objectClass.mapProperties;
for (NSString *key in jsonKeys.allKeys) {
NSString *objectProperty = jsonKeys[key];
NSString *value = o[key];
if (value)
[entity setValue:value forKey:objectProperty];
}
[result addObject:entity];
}
return (NSArray*)result;
}
Итак, я сообщаю синтаксический анализатор/сопоставление следующим образом:
NSArray *objects = [ObjectParser parseData:self.responseData intoObjectsOfType:ObjectEntity.class];
Это означает, что синтаксический анализатор должен знать, что такое мой корневой объект, что прекрасно, так как объект, получающий его из веб-службы, имеет эти знания.
Вышеописанное работает только для JSON без вложенных объектов, я пытаюсь создать парсер так, чтобы он учитывал отношения, а также создавал необходимые объекты и вставлял их в корневой объект, это должно быть рекурсивным и я продолжаю тупик.
Мне бы хотелось, чтобы я мог подходить к этому или любому пониманию, как будто что-то вроде этого существует как библиотека. Возможно, для использования или, возможно, для решения проблем, с которыми у меня возникают проблемы.
Спасибо заранее.
Ответы
Ответ 1
Почему бы не добавлять сопоставления для классов?
+ (NSDictionary *)mapClasses {
return @{
@"category": [Category class],
// ...
};
}
Для свойств, отличных от контейнера, вы можете даже выполнить проверку свойств среды выполнения, чтобы избежать избыточных сопоставлений.
Свойства контейнера могут отображаться в специальных объектах-оболочках:
[OMContainer arrayWithClass:Key.class], @"keys",
[OMContainer dicionaryWithKeyClass:ScopeID.class valueClass:Scope.class], @"possibleScopes",
Или даже блоки для динамического выбора типов:
[OMDynamicType typeWithBlock:^(id obj){
if ([obj isKindOfClass:NSString.class] && [obj hasPrefix:@"foo"])
return Foo.class;
else
return Bar.class;
}], @"foo",
Реализация этого может выглядеть примерно так:
+ (NSArray *)parseData:(NSData*)jsonData intoObjectsOfType:(Class)objectClass {
NSArray *parsed = /* ... */
NSMutableArray *decoded = [NSMutableArray array];
for (id obj in parsed) {
[decoded addObject:[self decodeRawObject:parsed intoObjectOfType:objectClass]];
}
return decoded;
}
+ (id)decodeRawObject:(NSDictionary *)dict intoObjectOfType:(Class)objectClass {
// ...
NSDictionary *jsonKeys = objectClass.mapProperties;
NSDictionary *jsonClasses = objectClass.mapClasses;
for (NSString *key in jsonKeys.allKeys) {
NSString *objectProperty = jsonKeys[key];
NSString *value = dict[key];
if (value) {
id klass = jsonClasses[key];
if (!klass) {
[entity setValue:value forKey:objectProperty];
} else if (klass == klass.class) {
[entity setValue:[self decodeRawObject:value intoObjectOfType:klass]
forKey:objectProperty];
} else if (/* check for containers and blocks */) {
// ...
}
}
}
// ...
}
Ответ 2
Рассмотрим использование RestKit: http://restkit.org
В этой структуре есть все, что вам нужно - абстракции REST и JSON, сопоставление объектов, даже поддержка Core Data и множество действительно полезных вещей, все они реализованы в настраиваемом и элегантном стиле.
UPDATE: Хорошо, при написании еще одного метода сопоставления я решил, что больше не могу этого делать и сделал небольшую структуру. Он анализирует свойства объекта, и с небольшой настройкой дает вам бесплатное красивое описание, isEqual/hashCode, бесплатную поддержку NSCoding и позволяет генерировать/от JSON-mappers (нормально, на самом деле, NSDictionary, но кто будет использовать его для чего-то еще). Все проверки NSNull, отсутствующие поля в JSON, новые неожиданные поля в JSON обрабатываются изящно и сообщаются правильно.
Если кто-то хочет, чтобы это было общедоступно, вы могли бы дать мне несколько комментариев или комментариев. Я бы сделал это в конце концов, но я мог бы подумать об этом быстрее.
Ответ 3
Я попробовал тот же подход также. Моей главной проблемой было точное описание типов. Например, для вложенных массивов я использовал что-то вроде этого:
@{ @"friends" : @[[Person class]] }
Но все стало грязно, потому что мне нужно было придумывать все больше и больше специального синтаксиса для разных типов, которые я хотел поддерживать. В конце концов я перестал следовать этому подходу и пошел к другому решению - также потому, что проверки во время выполнения, которые мне нужно было сделать, замедляли преобразование.
Есть много решений прямо сейчас. Я бы посоветовал взглянуть на потрясающий сайт iOS. И я также хотел бы отметить JSON Class Generator, потому что он позволяет вам
- точно опишите ваши типы (специальные картографы для даты и т.д. включены),
- типы проверяются автоматически (сообщается о несоответствиях и предусмотрены запасные значения),
- вам не нужно тратить время на написание повторяющегося кода (а сгенерированный код, кажется, всегда "просто работает")
Я ненавижу рекламировать, но этот инструмент сэкономил мне много работы, которую я вложил в другие функции своих приложений. Это вышло немного поздно, теперь, когда мир, похоже, переключился на Swift, но это еще одна причина не тратить время на написание моделей в Objective-C.
Вот несколько примеров кода, преобразующих в/форму JSON:
// converting MyClass <-> NSDictionary
MyClass *myClass = [MyClass myClassWithDict:someJsonDictionary];
NSDictionary *jsonDict = [myClass toDict];
// converting NSDictionary <-> NSData
NSDictionary *someJsonDictionary = [NSDictionary api_dictionaryWithJson:someJsonData];
NSData *jsonData = [someJsonDictionary api_toJson];
Функции, упомянутые в предыдущем комментарии, также поддерживаются JSON Class Generator:
-isEqual, -hash, -description, -copy, NSCoding/NSSecureCoding/NSKeyedArchiver
поддержка бесплатна и проверяет отсутствующие/дополнительные /NSNull/необязательные поля и неправильные типы в ответе JSON, сообщает об ошибках, изящно терпит неудачу с запасными значениями и делает это с помощью диспетчеризации метода, а не самоанализа типа во время выполнения (что быстрее).
Ответ 4
Попробуйте CSMapper, это потрясающе! Вы просто создаете слои, названные так же, как класс, отображаете некоторые свойства, затем вы легко можете сопоставить словарь с объектами с помощью одной строки кода. Я тестировал его по многим проектам и считал его очень чистым, исполнительным. Это дало мне возможность легко реагировать на изменения API в течение жизненного цикла разработки.
https://github.com/marcammann/CSMapper
В настоящее время я обновляю документацию и добавляю к проекту на личную вилку, которая, надеюсь, вскоре будет объединена:)
https://github.com/AntonTheDev/CSMapper
Ответ 5
Моя рекомендация - использовать категорию Motis в NSObject. Он легкий, выполняет автоматическую проверку типов объектов и очень прост в использовании.
В этом другом вопросе есть вопрос: Отображение объектов JSON в пользовательских объектах
Надеюсь, это полезно.