serialize objective-c custom object to JSON for OSX? - objective-c

I'm very new to Mac development and I'm having some troubles finding good resources. My current problem is a custom objective-c class object serialization to JSON.
I know that there is a built-in serializer in apple libraries, but that one works only with Foundation objects.
In my case I have my own class that looks like this:
#interface SomeClass : NSObject
{
int a;
int b;
NSString *aa;
NSString *bb;
}
#property int a,b;
#property NSString *aa,*bb;
#end
If anyone knows how to serialize this type of structure to a JSON please give me a hint! Any kind of relevant information would help! Thank you!

If you just want to serialize an object that contains integers and strings, the easiest way is to create a data structure that NSJSONSerialization supports and serialize that:
static const NSString *kAKey = #"a";
static const NSString *kBKey = #"b";
static const NSString *kAaKey = #"aa";
static const NSString *kBbKey = #"bb";
- (id)JSONObject {
return #{
kAKey: #(self.a),
kBKey: #(self.b),
kAaKey: self.aa,
kBbKey: self.bb
};
}
- (NSData *)JSONData {
return [NSJSONSerialization dataWithJSONObject:[self JSONObject] options:0 error:NULL];
}

I have been looking into this for the past week. I decided to write my own solution. It is very simple and built upon existing Apple functionality.
See here: https://github.com/gslinker/GSObject
And here: http://digerati-illuminatus.blogspot.com/2016/01/objective-c-and-json-convert-subclass.html
For your data model object have it inherit from GSObject instead of NSObject. Here is an example of ThingOne with inherits from GSObject:
ThingOne* object1 = [[ThingOne alloc] init];
object1.name = #"John Jones";
NSData* jsonData1 = [object1 toJsonDataWithOptions:NSJSONWritingPrettyPrinted];
NSString *jsonString1 = [object1 toJsonStringWithOptions:NSJSONWritingPrettyPrinted];
NSDictionary<NSString *,id> *dict1 = [GSObject dictionaryWithValues:object1];
NSString *roundTripJson1 = [object1 toJsonStringWithOptions:NSJSONWritingPrettyPrinted];
//
// ThingOne.h
// JasonStuff
//
// Created by Geoffrey Slinker on 12/28/15.
// Copyright © 2015 Slinkworks LLC. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "GSObject.h"
#import "ThingTwo.h"
#interface ThingOne : GSObject
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) ThingTwo *thingTwo;
#property (nonatomic, retain) NSArray *values;
#property (nonatomic, retain) NSDictionary *dict;
#property int myInt;
#property float myFloat;
#property BOOL myBool;
#property (nonatomic, retain) NSNumber* someMoney;
#end
//
// ThingOne.m
// JasonStuff
//
// Created by Geoffrey Slinker on 12/28/15.
// Copyright © 2015 Slinkworks LLC. All rights reserved.
//
#import "ThingOne.h"
#implementation ThingOne
#synthesize name;
#synthesize thingTwo;
#synthesize values;
#synthesize dict;
#synthesize myInt;
#synthesize myFloat;
#synthesize myBool;
#synthesize someMoney;
- (instancetype)init
{
self = [super init];
thingTwo = [[ThingTwo alloc] init];
thingTwo.stuff = #"Thing Two Stuff";
thingTwo.someOtherStuff = #"Thing Two Other Stuff";
NSDateFormatter *dateFormater = [[NSDateFormatter alloc]init];
[dateFormater setDateFormat:#"yyyy-mm-dd"];
thingTwo.someDate = [dateFormater dateFromString:#"1963-10-07"];
values = [NSArray arrayWithObjects:#"Value1", #"Value2", #"Value3", nil];
dict = [NSDictionary dictionaryWithObjectsAndKeys:#"value1", #"key1", #"value2", #"key2", nil];
myInt = 5431;
myFloat = 123.456f;
myBool = YES;
someMoney = [NSNumber numberWithInt:503];
return self;
}
#end
//
// ThingTwo.h
// JasonStuff
//
// Created by Geoffrey Slinker on 12/28/15.
// Copyright © 2015 Slinkworks LLC. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "GSObject.h"
#interface ThingTwo : GSObject
#property (nonatomic, retain) NSString *stuff;
#property (nonatomic, retain) NSString *someOtherStuff;
#property (nonatomic, retain) NSDate *someDate;
#property (nonatomic, retain) NSString *nullString;
#property (nonatomic, retain) NSDate *nullDate;
#end
//
// ThingTwo.m
// JasonStuff
//
// Created by Geoffrey Slinker on 12/28/15.
// Copyright © 2015 Slinkworks LLC. All rights reserved.
//
#import "ThingTwo.h"
#implementation ThingTwo
#synthesize stuff;
#synthesize someOtherStuff;
#synthesize someDate;
- (instancetype)init
{
self = [super init];
someDate = [NSDate date];
return self;
}
#end
Here is an example of the JSON output:
{
"values" : [
"Value1",
"Value2",
"Value3"
],
"myInt" : 5431,
"myFloat" : 123.456,
"myBool" : true,
"someMoney" : "$503.00",
"thingTwo" : {
"stuff" : "Thing Two Stuff",
"nullDate" : null,
"someDate" : "1963-01-07 07:10:00 +0000",
"nullString" : null,
"someOtherStuff" : "Thing Two Other Stuff"
},
"name" : "John Jones",
"dict" : {
"key1" : "value1",
"key2" : "value2"
}
}

Related

Why is my JSONModel Class being treated as a NSDictionary?

This is my JSON fetched from the API:
{
categories = (
{
icon = {
prefix = "https://ss3.4sqi.net/img/categories_v2/food/italian_";
suffix = ".png";
};
id = 4bf58dd8d48988d110941735;
name = "Italian Restaurant";
pluralName = "Italian Restaurants";
primary = 1;
shortName = Italian;
}
);
hasPerk = 0;
id = 4b5bed2ef964a520971d29e3;
location = {
address = "HS-5";
cc = IN;
city = "New Delhi";
country = India;
crossStreet = "Kailash Colony Market";
distance = 4061;
formattedAddress = (
"HS-5 (Kailash Colony Market)",
"New Delhi 110024",
Delhi,
India
);
labeledLatLngs = (
{
label = display;
lat = "28.55272788981442";
lng = "77.24192322690806";
}
);
lat = "28.55272788981442";
lng = "77.24192322690806";
postalCode = 110024;
state = Delhi;
};
name = "The Big Chill Cafe";
referralId = "v-1546605733";
}
And here is my model class:
#interface LocationModel : JSONModel
#property (nonatomic) NSInteger distance;
#property (nonatomic) NSInteger postalCode;
#property (nonatomic) NSString *address;
#property (nonatomic) NSString *cc;
#property (nonatomic) NSString *city;
#property (nonatomic) NSString *country;
#property (nonatomic) NSString *crossStreet;
#property (nonatomic) NSString *lat;
#property (nonatomic) NSString *lng;
#property (nonatomic) NSString *state;
#property (nonatomic) NSArray *formattedAddress;
#property (nonatomic) NSArray *labeledLatLngs;
#end
#protocol VenueModel;
#interface VenueModel : JSONModel
#property (nonatomic) NSArray *categories;
#property (nonatomic) BOOL hasPerk;
#property (nonatomic) NSString *id;
#property (nonatomic) LocationModel *location;
#property (nonatomic) NSString *name;
#property (nonatomic) NSString *referralId;
#end
#interface Restaurant : JSONModel
#property (nonatomic) NSInteger confident;
#property (nonatomic) NSArray <VenueModel*> *venues;
#end
Now, I am trying to get the values like this:
restaurant = [[Restaurant alloc] initWithDictionary
[NSJSONSerialization JSONObjectWithData:JSON
options:NSJSONReadingAllowFragments error:nil][#"response"] error:&error];
VenueModel *obj = [restaurant.venues firstObject];
LocationModel *locationObj = obj.location;
The values are coming fine till the VenueModel object, after that when I am trying to access the LocationModel object, i.e
LocationModel *locationObj = obj.location
, its throwing the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI location]: unrecognized selector sent to instance 0x600001b82880
Somebody please help me out, I need the location object.
NSJSONSerialization will produce NSDictionaries, NSArrays, NSStrings, NSNumbers and a few other basic types from a JSON string.
Your idea to initialize a custom object ([[Restaurant alloc] initWithDictionary...) is the right idea, but that idea must be applied to nested structures as well. So, for example, the Restaurant initializer must apply that idea to its venues array.
Otherwise, you'll get the crash you're getting, treating one of the venue objects as if its a VenueModel instead of what it really is (an NSDictionary).
You didn't post initWithDictionary, but it needs to look something like this, especially the part where the objects it contains are initialized...
- (id)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
// ...
self.venues = [NSMutableArray array];
if (self) {
for (NSDictionary *venueDictionary in dictionary[#"venues"]) {
VenueModel *venue = [[VenueModel alloc] initWithDictionary:venueDictionary];
[self.venues addObject:venue];
}
}
// and so on, for every other object type nested in dictionary
return self;
}
Moreover, as you can see, VenueModel must also follow this practice, turning the location data into a LocationModel, and so on...

How can I customize the NSdictionary using Mantle?

I have the following json string:
{"suit_id": 2427;
"suits": "http://img.prettyyes.com/1137-4930-1446175512.jpeg;http://img.prettyyes.com/1137-7665-1446175512.jpeg;http://img.prettyyes.com/1137-4783-1446175512.jpeg"}
So when use mantle to parse the json string with following file
testModel.h
#interface testModel : MTLModel <MTLJSONSerializing>
#property (nonatomic, strong) NSString *suit_id;
#property (nonatomic, strong) NSString *suits;
#end
testModel.m
#implementation testModel
+ (NSDictionary *) JSONKeyPathsByPropertyKey {
return #{
"suit_id": "suit_id",
"suits": "suits"
};
}
#end
How ever I would like to convert suits from string to a NSArray with multiple urls so I did following:
testModel.h
#interface testModel : MTLModel <MTLJSONSerializing>
#property (nonatomic, strong) NSString *suit_id;
#property (nonatomic, strong) NSArray *suits;
#end
testModel.m
#implementation testModel
+ (NSDictionary *) JSONKeyPathsByPropertyKey {
return #{
"suit_id": "suit_id",
"suits": "suits"
};
}
+ (NSValueTransformer *) suitsJSONTransform {
return [MTLValueTransformer transformerUsingForwardBlock:^(NSString *str, BOOL *success, NSError **error){
return [[str componentsSeparatedByString:#";"] mutableCopy];
}];
}
#end
But it's not working. The result is nil. When I override the wrong function?
The method returning the NSValueTransformer for suits should be called suitsJSONTransformer, not suitsJSONTransform.
The format for the method name is <propertyName>JSONTransformer.

NSMutableArray can not add object?

I have 2 model:
#interface Program : NSObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, strong) NSMutableArray *guides;
#end
#interface Guide : NSObject
#property (nonatomic, retain) NSString *name;
#end
And I add some guides to program from one xml:
Program *program = [Program new];
program.name = #"My list"
for(DDXMLElement *guideElement in [programElement nodesForXPath:#"guide" error:&error])
{
Guide *guide = [Guide new];
guide.name = [guideElement stringValue];// [p attribute:#"name"];
[program.guides addObject:guide];
NSLog(#"load guide number: %d", [program.guides count]);
}
The out is always "load guide number: 0"
program.guides is nil, since you never created it.
In your Program's init method, add:
self.guides = [[NSMutableArray alloc] init];
Or, more sloppily, before your for loop add:
program.guides = [[NSMutableArray alloc] init];

Store scalar values (NSInteger, BOOL, double) in CoreData without converting to NSNumber

I have read a lot of articles where they say to explicitely convert from and to NSNumber when I want to store scalars in CoreData:
#property (nonatomic, assign) NSInteger value;
- (NSInteger)value
{
return [value integerValue];
}
- (void)setValue:(NSInteger)val
{
value = [NSNumber numberWithInteger:val];
}
But in our old project we have a bunch of properties where we doesn't do those manipulations (and they don't have custom accessors)! Why it works?
Example code
Declaration. Scalar values are not transient.
#interface ProductProperty : NSManagedObject
#property (nonatomic, strong, readonly) NSString * propertyID;
#property (nonatomic, strong, readonly) NSString * title;
#property (nonatomic, strong, readonly) NSSet * values;
#property (nonatomic, assign, readonly) BOOL filter;
#property (nonatomic, strong, readonly) NSDate *update;
#property (nonatomic, strong, readonly) NSNumber *index;
#property (nonatomic, assign, readonly) BOOL system;
#end
#import "ProductProperty.h"
#implementation ProductProperty
#dynamic propertyID;
#dynamic title;
#dynamic values;
#dynamic filter;
#dynamic update;
#dynamic index;
#dynamic system;
#end
Mapping into objects. Called if received JSON differs from existing. Otherwise it fetches from the CoreData storage.
- (void)updateProperties:(NSArray*)properties
{
for (NSDictionary *_property in properties) {
NSString *propertyID = [_property objectForKey:#"id"];
ProductProperty *property = [state.productPropertiesWithIDs objectForKey:propertyID];
if (!property) {
property = [state.ctx newObjectWithEntityName:ProductProperty.entityName];
property.propertyID = propertyID;
[state.productPropertiesWithIDs setObject:property forKey:propertyID];
}
property.update = state.update;
property.title = [_property objectForKey:#"title"];
property.filter = [_property objectForKey:#"filter"] ? [[_property objectForKey:#"filter"] boolValue] : YES;
property.index = [propertyIndexes objectForKey:propertyID] ? [propertyIndexes objectForKey:propertyID] : [NSNumber numberWithInt:propertyIndex++];
property.system = [SYSTEM_PROPERTY_IDS containsObject:propertyID] ? YES : NO;
[self updatePropertyValues:[_property objectForKey:#"values"] forProperty:property];
}
}
- (ProductProperty*)productPropertyWithID:(NSString*)propertyId error:(NSError**)error
{
NSFetchRequest *req = [ProductProperty request];
req.predicate = [NSPredicate predicateWithFormat:#"propertyID == %#", propertyId];
return [[ctx executeFetchRequest:req error:error] lastObject];
}
The answer is that since iOS 5 CoreData support auto generating accessors for scalars so I don't need to implement them manually.

NSMutableArray: add and extract struct

I'm trying to store some data in an NSMutableArray. This is my struct:
typedef struct{
int time;
char name[15];
}person;
This is the code to add a person:
person h1;
h1.time = 108000;
strcpy(h1.name, "Anonymous");
[highscore insertObject:[NSValue value:&h1 withObjCType:#encode(person)] atIndex:0];
So, I try to extract in this way:
NSValue * value = [highscore objectAtIndex:0];
person p;
[value getValue:&p];
NSLog(#"%d", p.time);
The problem is that the final log doesn't show me 108000!
What is wrong?
Your code looks correct (and works for me), so I deduce that you aren't initializing highscore. So when you send the insertObject:atIndex: message to it, nothing happens. When you then send the objectAtIndex: method to it, you get nil back. When you send getValue: to the nil NSValue *value, it does nothing, so your person p is left filled with random stack garbage, which is why your NSLog doesn't print 108000.
As stated in my initial comment there rarely is a reason to do this kind of stuff with pure c structs. Instead go with real class objects:
If you're unfamiliar with the syntax below you may want to look at these quick tutorials on ObjC 2.0 as well as read Apple's documentation:
A Quick Objective-C 2.0 Tutorial
A Quick Objective-C 2.0 Tutorial: Part II
Person Class:
// "Person.h":
#interface Person : NSObject {}
#property (readwrite, strong, nonatomic) NSString *name;
#property (readwrite, assign, nonatomic) NSUInteger time;
#end
// "Person.m":
#implementation Person
#synthesize name = _name; // creates -(NSString *)name and -(void)setName:(NSString *)name
#synthesize time = _time; // creates -(NSUInteger)time and -(void)setTime:(NSUInteger)time
#end
Class use:
#import "Person.h"
//Store in highscore:
Person *person = [[Person alloc] init];
person.time = 108000; // equivalent to: [person setTime:108000];
person.name = #"Anonymous"; // equivalent to: [person setName:#"Anonymous"];
[highscore insertObject:person atIndex:0];
//Retreive from highscore:
Person *person = [highscore objectAtIndex:0]; // or in modern ObjC: highscore[0];
NSLog(#"%#: %lu", person.name, person.time);
// Result: "Anonymous: 108000"
To simplify debugging you may also want Person to implement the description method:
- (NSString *)description {
return [NSString stringWithFormat:#"<%# %p name:\"%#\" time:%lu>", [self class], self, self.name, self.time];
}
which will allow you to just do this for logging:
NSLog(#"%#", person);
// Result: "<Person 0x123456789 name:"Anonymous" time:108000>
Reimplement Person as an Objective-C object and reap the benefits:
Person.h:
#interface Person : NSObject
{
int _time;
NSString *_name;
}
#property (assign, nonatomic) int time;
#property (retain, nonatomic) NSString *name;
#end
Person.m:
#import "Person.h"
#interface Person
#synthesize time = _time;
#synthesize name = _name;
- (id)init
{
self = [super init];
if (self != nil)
{
// Add init here
}
return self;
}
- (void)dealloc
{
self.name = nil;
[super dealloc];
}
#end