I declared DATA as below
#property (strong, nonatomic) NSDictionary *DATA;
My table in console DATA is :
(
{
comment = "";
deadline = deadline;
id = 2;
responsible = "responsable action 1";
status = pending;
title = "";
}
)
But problem in NSLog(#"%#",[DATA objectForKey:#"deadline"]);
You are using this DATA object as a NSDictionary but it is a NSArray. You can check your log which is start from ( which indicate it is a Array.
You can also check like this,
if ([DATA isKindOfClass:[NSDictionary class]]) {
//then this is dictionary
}else{
// other datatype
}
before use as NSLog(#"%#",[DATA objectForKey:#"deadline"]).
If you want to access for now you can use as, but for that this DATA should be as NSArray or NSMutableArray.
#property (strong, nonatomic) NSArray *DATA;
[[DATA objectAtIndex:0] objectForKey:#"deadline"]
The above JSON response is an Array and not a Dictionary, () = Array and {} = Dictionary.
Ideally, you should have something like,
{
"comment":"",
"deadline":"deadline",
"id":2,
"responsible":"responsable action 1",
"status":"pending",
"title":""
}
Still, you can try using an array instead of a dictionary and check if it gives you an error while parsing.
Related
This should be straightforward but something is preventing me from filtering an array of custom objects by NSNumber using NSPredicate. Perhaps it has something to do with the datatype when converting from JSON but I can't figure it out.
I download data from a JSON in an array of custom Objects that look like:
{"hid":"47","public":"1"}
The code to parse the JSON looks like:
if (feedElement[#"public"] && ![feedElement[#"public"] isEqual:[NSNull null]]) {
newMyObject.pub = feedElement[#"public"]== nil ? #"0" : feedElement[#"public"];}
The object looks like:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyObject : NSObject
#property (nonatomic, retain) NSNumber * hid;
#property (nonatomic, retain) NSNumber * pub;
#end
NS_ASSUME_NONNULL_END
The objects are placed in an NSArray * myObjects
My NSPredicate and filter code looks like:
NSPredicate *pubPred = [NSPredicate predicateWithFormat:#"pub == 1"];
NSArray *filteredArray = [myObjects filteredArrayUsingPredicate:pubPred];
When I log [myObjects valueForKey:#"pub"], it logs as 1,1,1, etc. so I know that the values for pub are all 1 however the resulting filteredArray is empty.
What could be wrong with my code?
Thanks for any suggestions.
Edit: I changed public to pub in the object in case public was a reserved word but it did not change anything
With sample code : {"hid":"47","public":"1"}
newMyObject.pub = feedElement[#"public"]== nil ? #"0" : feedElement[#"public"];
In case of public value is present in JSON, you'll set pub property with feedElement[#"public"], which means, it will be #"1" (with the sample), which means you'll put a NSString.
In case of public value is present in JSON, you'll set pub property with #"0" which means you'll put a NSString.
It doesn't matter if it's declared #property (nonatomic, retain) NSNumber * pub;, you are setting a NSString, not a NSNumber.
Want some code testing?
#interface MyObject : NSObject
#property (nonatomic, retain) NSNumber *hid;
#property (nonatomic, retain) NSNumber *pub;
+(void)test;
#end
And
#implementation MyObject
-(id)initWithPub:(NSNumber *)pub andHID:(NSNumber *)hid {
self = [super init];
if (self) {
self.pub = pub;
self.hid = hid;
}
return self;
}
-(NSString *)description {
return [NSString stringWithFormat:#"%# pub: %# - hid: %#", [super description], [self pub], [self hid]];
}
+(NSArray *)arrayList {
return #[[[MyObject alloc] initWithPub:#1 andHID:#3], //Here with real NSNUmbner
[[MyObject alloc] initWithPub:#"1" andHID:#"2"]]; //Here with NSString instead
}
+(void)test {
NSArray *list = [MyObject arrayList];
NSPredicate *predicateNSNumber = [NSPredicate predicateWithFormat:#"pub == %#", #1];
NSArray *filteredNSNumber = [list filteredArrayUsingPredicate:predicateNSNumber];
NSLog(#"Filtered-NSNumber: %#", filteredNSNumber);
NSPredicate *predicateNSString = [NSPredicate predicateWithFormat:#"pub == %#", #"1"];
NSArray *filteredNSString = [list filteredArrayUsingPredicate:predicateNSString];
NSLog(#"Filtered-NSString: %#", filteredNSString);
}
Output:
$> Filtered-NSNumber: (
"<MyObject: 0x60000024cba0> pub: 1 - hid: 3"
)
$> Filtered-NSString: (
"<MyObject: 0x60000024d1e0> pub: 1 - hid: 2"
)
You can state: "Yeah, but you have a warning in [[MyObject alloc] initWithPub:#"1" andHID:#"2"]: Incompatible pointer types sending 'NSString *' to parameter of type 'NSNumber *', yes, I have. You should have it too.
In fact, you'll have it too if you wrote your ternary if :
if (feedElement[#"public"] == nil) {
newMyObject.pub = #"0"; //Incompatible pointer types assigning to 'NSNumber * _Nonnull' from 'NSString *'
} else {
newMyObject.pub = feedElement[#"public"]; //Here, no warning, because it's hope you know what you are doing and setting a NSNumber here.
}
What about using this to your code to check:
for (MyObject *anObject in list) {
NSLog(#"Testing: %#", anObject);
if ([[anObject pub] isKindOfClass:[NSString class]]) {
NSLog(#"Pub is a String");
} else if ([[anObject pub] isKindOfClass:[NSNumber class]]) {
NSLog(#"Pub is a NSNumber");
}
NSLog(#"Attempt to call -stringValue which is a NSNumber method, not a NSString one");
NSLog(#"Attempt Call: %#\n", [[anObject pub] stringValue]);
}
You should get a -[__NSCFConstantString stringValue]: unrecognized selector sent to instance error, because it's really a NSString, not a NSNumber.
Solutions:
You need to fix your parsing, or change the type of property in MyObject.
With keeping the property as a NSNumber:
if ([feedElement[#"public"] isKindOfClass:[NSString class]]) {
self.pub = #([feedElement[#"public"] integerValue]); //Transform it into a NSInteger, then into a NSNumber with #(someInteger)
} else ([feedElement[#"public"] isKindOfClass:[NSNumber class]]) {
self.pub = feedElement[#"public"]; //It's already a NSNumber instance
} else {
self.pub = #(0); //Default value because unknown class or not present
}
This question already has answers here:
Sort NSArray of custom objects based on sorting of another NSArray of strings
(5 answers)
Closed 7 years ago.
I have an NSArray of unique UUIDs sorted in the proper order. I also have another NSArray of bookmarks with a UUID encoding. I would like to sort my NSArray of bookmarks based on their UUID property into groups.
For example, I have an NSArray: #[#"uuid1", #"uuid3", #"uuid2"] that has been sorted into this particular order. Now the other NSArray must sort all of its bookmarks in the same order as the first NSArray above.
So the second NSArray is: #[#"bookmark1", #"bookmark2", #"bookmark3", ...etc.]
Say bookmark 1 has the UUID property encoded as uuid2, bookmark 2 has the UUID encoding of uuid 1, but the bookmark 3 has encoding of uuid3. How can I sort and group these bookmarks so that it would be: #[#"bookmark2", #"bookmark3", #"bookmark1"]?
Thanks!
You could get rid of the second array and use a dictionary instead, which is keyed on the UUID.
NSArray *sortedIDs = #[ #"uuid1", #"uuid3", #"uuid2", ];
NSDictionary *items = #{
#"uuid1" : #[ bookmark1 ],
#"uuid2" : #[ bookmark2 ],
#"uuid3" : #[ bookmark3 ],
};
Now when you want the second bookmark you can access it with
NSArray *bookmarksForUUID = items[sortedIDs[1]];
If you wanted to build the structure above you could add a category like the below to NSArray
- (NSDictionary *)pas_groupBy:(id (^)(id object))block;
{
NSParameterAssert(block);
NSMutableDictionary *groupedDictionary = NSMutableDictionary.new;
for (id object in self) {
id key = block(object);
if (groupedDictionary[key]) {
[groupedDictionary[key] addObject:object];
} else {
groupedDictionary[key] = [NSMutableArray arrayWithObject:object];
}
}
return groupedDictionary;
}
Then assuming your bookmark objects look something like
#interface Bookmark : NSObject
#property (nonatomic, copy) NSString *UUID;
// other properties
// other properties
#end
You can use the category like this
NSDictionary *bookmarksMappedBySection = ({
return [bookmarks pas_groupBy:^(Bookmark *bookmark) {
return bookmark.UUID;
};
});
Create a class Bookmark. Add both the UUIDs and title as properties to this class. Now you can have just one array which holds objects of class Bookmark and each one will have access to its UUID and title (as well as any other properties you might want to add later on).
#interface Bookmark: NSObject
#property (nonatomic,strong) NSString *uuid;
#property (nonatomic,strong) NSString *title;
#end
Now if you have NSArray (lets call it myBookmarks) you can do the following:
for (Bookmark *bookmark in myBookmarks) {
NSLog(#"my UUID is %#",bookmark.uuid);
NSLog(#"my title is %#",bookmark.title);
}
If you need to store these items in NSUserDefaults all you have to do is adopt the NSCoding protocol. If you don't know how to do it here is a great and simple example.
I am using Realm 0.92.3 but it crashed when I have null value despite I have set the default properties. Is there any solution on this? If not I might convert using coredata as this is very important to me. The null will be random on several properties
#interface WatchlistNews : RLMObject
#property NSString *nid;
#property NSString *tid;
#property NSString *country;
#end
#implementation WatchlistNews
+ (NSString *)primaryKey {
return #"nid";
}
+ (NSDictionary *)defaultPropertyValues {
return #{#"nid" : #"", #"tid": #"", #"country": #""};
}
#end
Data response:
nid = 509319;
tid = <null>;
country = my;
Error code:
-[NSNull UTF8String]: unrecognized selector sent to instance 0x10712b4c0
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull UTF8String]: unrecognized selector sent to instance 0x10712b4c0'
Realm do not support complex data types, so if you try to assign a complex value such as <null>, it gets crashed.
You should check the response you are getting from the server for the <null> values. And if it exists in the response replace it with an empty string. Try following code on the response you are getting, to remove the occurance of <null> values.
-(NSMutableDictionary *) removeNullFromDictionary:(NSDictionary *) originalDictionary{
NSArray *allKeysArray = [originalDictionary allKeys];
const NSMutableDictionary *replaced = [originalDictionary mutableCopy];
const id nul = [NSNull null];
const NSString *blank = #"";
for(NSString *key in allKeysArray) {
const id object = [originalDictionary objectForKey:key];
if(object == nul) {
[replaced setObject:blank forKey:key];
}
}
return [replaced copy];
}
Realm does not yet support setting nil for NSString properties, but you can track progress on that by following https://github.com/realm/realm-cocoa/issues/628.
I would like to map a json string to an anonymous object using the specific class. Suppose i have a country class. I would like to parse a json string into this object without knowing which object it is. So i use the class for parsing.
#interface CountryModel
#property (assign, nonatomic) int id;
#property (strong, nonatomic) NSString* country;
#end
NSString* json = (fetch here JSON from Internet) ...
CountryModel* country ;
id obj = country ;
obj = tojson( [obj class] , json )
https://github.com/icanzilb/JSONModel does what i need but i need same thing without using the inheritance. I would like to do same thing without inheriting from JSONModel;
You could define a Category for your custom model class (say, CountryModel) which implements a class factory method. A contrived example:
#interface CountryModel (JSONExtension)
+ (CountryModel*) jsonExtension_modelWithJSONObject:(NSDictionary*)jsonObject error:(NSError**)error;
#end
#implementation CountryModel (JSONExtension)
+ (CountryModel*) jsonExtension_modelWithJSONObject:(NSDictionary*)jsonObject error:(NSError**)error {
// Create an object of type Foo with the given NSDictionary object
CountryModel* result = [[CountryModel alloc] initWithName:jsonObject[#"name"]];
if (result == nil) {
if (error) {
*error = [NSError errorWithDomain:#"CountryModel"
code:-100
userInfo:#{NSLocalizedDescriptionKey: #"Could not initialize CountryModel with JSON Object"}];
}
return nil;
}
// "recursively" use jsonExtension_modelWithJSONObject:error: in order to initialize internal objects:
BarModel* bar = [BarModel jsonExtension_modelWithJSONObject:jsonObject[#"bar"] error:error];
if (bar == nil) // bar is required
{
result = nil;
return nil;
}
result.bar = bar;
return result;
}
#end
jsonObject is a representation of a JSON Object as a NSDictionary object. You need to first create this representation before passing it the class factory method, e.g.:
NSError* error;
NSDictionary* jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
assert([jsonObject isKindOfClass[NSDictionary class]]);
CountryModel* model = [CountryModel jsonExtension_modelWithJSONObject:jsonObject error:&error];
My problem is that I have a class property that is of type NSMutableArray, as defined in my header file, yet when I attempt to modify one of the array elements (an NSDictionary) I receive the following runtime error:
2013-01-16 14:17:20.993 debtaculous[5674:c07] * Terminating app due
to uncaught exception 'NSInternalInconsistencyException', reason:
'-[__NSCFArray replaceObjectAtIndex:withObject:]: mutating method sent
to immutable object'
Header declaration:
// BudgetViewController.h
#import <UIKit/UIKit.h>
#interface BudgetViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
- (IBAction)afterTaxIncomeEditingDidEnd:(id)sender;
#property (strong, nonatomic) NSMutableArray *budgetArray;
#property (strong, nonatomic) IBOutlet UITextField *afterTaxIncome;
#property (strong, nonatomic) IBOutlet UITableView *budgetTableView;
#end
Method that generates the error:
-(void)applyCCCSWeights
{
NSMutableDictionary *valueDict;
NSString *newAmount;
for (id budgetElement in [self budgetArray]) {
valueDict = [[NSMutableDictionary alloc] initWithDictionary:budgetElement];
newAmount = [NSString stringWithFormat:#"%0.2f", [[self afterTaxIncome].text floatValue] * [[budgetElement objectForKey:#"cccs_weight"] floatValue]];
[valueDict setValue:newAmount forKeyPath:#"amount"];
[[self budgetArray] replaceObjectAtIndex:0 withObject:valueDict];
NSLog(#"%0.2f (%0.2f)", [[budgetElement objectForKey:#"amount"] floatValue], [[self afterTaxIncome].text floatValue] * [[budgetElement objectForKey:#"cccs_weight"] floatValue]);
}
[self.budgetTableView reloadData];
}
// Note the replaceObjectAtIndex:0 above is just a placeholder. This will be replaced with the correct index.
budgetArray is surely immutable, you have to create it mutable.
Probably you're doing something like this:
budgetArray= [NSArray arraWithObjects; obj1, obj2, nil];
And ignoring the compiler warning. Make it mutable:
budgetArray= [[NSMutableArray alloc]init];
I'm fairly certain you cannot change a mutable object during enumeration.
This SO question may help: Setting an object during fast enumeration problems
In your init method, put this:
budgetArray = [[NSMutableArray alloc] init];
Also, why not use dictionary and array literal syntax?
-(void)applyCCCSWeights {
NSMutableDictionary *valueDict;
NSString *newAmount;
for (NSDictionary *budgetElement in [self budgetArray]) {
valueDict = [budgetElement mutableCopy];
newAmount = [NSString stringWithFormat:#"%0.2f", [[self afterTaxIncome].text floatValue] * [budgetElement[#"cccs_weight"] floatValue]];
valueDict[#"amount"] = newAmount;
_budgetArray[0] = valueDict;
NSLog(#"%0.2f (%0.2f)", [budgetElement[#"amount"] floatValue], [[self afterTaxIncome].text floatValue] * [budgetElement[#"cccs_weight"] floatValue]);
}
[self.budgetTableView reloadData];
}
Notice that [[self budgetArray] replaceObjectAtIndex:0 withObject:valueDict];
becomes: _budgetArray[0] = valueDict;
You can't change an array while doing a fast iteration over the array. On the other hand, it's entirely unnecessary; the code is absolutely inefficient: Just make the elements of the array NSMutableDictionaries, then change the dictionaries directly, instead of creating a copy and then changing an element in the copy.
Noticed later that you use NSJSONSerialization; look at the flags and don't pass 0 blindly.