Exception 'Invalid type in JSON write - ios

I have an interface that looks like this: #import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface AVBase : NSObject
#property NSString *portName;
#property NSString *uid;
#property NSString* port;
- (id) initWithPortName:(NSString *)portName andUID:(NSString *)uid andPort:(AVAudioSessionPort)port;
#end
NS_ASSUME_NONNULL_END
and the .m file
#implementation AVBase
- (id)initWithPortName:(NSString *)portName andUID:(NSString *)uid andPort:(AVAudioSessionPort)port
{
self = [super init];
if (self)
{
self.portName = portName;
self.uid = uid;
self.port = [port description];
}
return self;
}
#end
I want to create an array of current outputs for the AVAudioSession, so I do it like this:
NSMutableArray *myArray = [[NSMutableArray alloc] init];
AVAudioSession *session = AVAudioSession.sharedInstance;
NSArray *outputs = [[session currentRoute] outputs];
for(AVAudioSessionPortDescription* output in outputs)
{
AVBase* av = [AVBase alloc];
av = [av initWithPortNumber:output.portName andUID:output.UID andPort:output.portType];
[myArray addObject:av];
}
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:myArray options:NSJSONWritingPrettyPrinted error:&error];
But when I try to serialize myArray I get an error that says:
Exception 'Invalid type in JSON write (AVBase)
I don't understand what's wrong, all the properties in my class are of type NSString so it should work.

NSJSONSerialization accepts only NSArray, NSDictionary, NSString, NSNumber (and NSNull), for its top level, but all sublevels/subproperties too.
myArray is a NSArray of AVBase, and AVBase isn't one of them.
You need to convert an AVBase into a NSDictionary first.
-(NSDictionary *)toDict {
return #{#"portName": portName, #"uid": uid, #"port": port};
}
Then:
[myArray addObject:[av toDict]];
If you don't use AVBase, or just for it, you can construct the NSDictionary directly from AVAudioSessionPortDescription *output, no need to use the AVBase here.

Related

Objective-C: Selectively combine two arrays

I am aware of how normal NSArray concatenation works in Objective-C. This is not that question.
I have data that is being incrementally updated from a web service. My object has the following class definition (with a lot removed):
// NoteTemplate.h
#import <UIKit/UIKit.h>
#interface NoteTemplate
#property (copy, nonatomic) NSString *objectId;
I am caching a list of these on-device and checking at launch to see if there are any new or updated NoteTemplate objects in my database to load. So, I end up with two arrays:
NSArray <NoteTemplate *> *oldArray
NSArray <NoteTemplate *> *newArray
If there are no updates, then all I need to do is simply concatenate the two arrays together and that's that.
If there are updates, however, I want to combine the two arrays, but whenever there is a common objectId, the item in newArray should take precedence over the item in oldArray.
Thus far, I am brute-forcing it like this:
- (void)updateNoteTemplatesWithArray:(NSArray *)newTemplates {
NSArray *oldTemplates = [self getNoteTemplates];
NSMutableArray *combined = [NSMutableArray arrayWithArray:newTemplates];
for (NoteTemplate *noteTemplate in oldTemplates) {
NSArray *matches = [combined filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id blockTemplate, NSDictionary<NSString *,id> *bindings) {
return [((NoteTemplate *)blockTemplate).objectId isEqualToString:noteTemplate.objectId];
}]];
if (matches.count == 0) {
[combined addObject:noteTemplate];
}
}
[self setNoteTemplates:[combined copy]];
}
Is there a more optimized way to do this? I can't see that this will affect performance at all, so perhaps an optimization is unnecessary. Still, this approach feels hacky and way over-engineered.
To extend #Larme's suggestion with Set usage you can try the following approach:
#interface NoteTemplate: NSObject
#property (copy, nonatomic) NSString *objectId;
#property (copy, nonatomic) NSString *text;
- (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text;
#end
#implementation NoteTemplate
- (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text {
self = [super init];
if (self != nil) {
_objectId = objectId;
_text = text;
}
return self;
}
- (BOOL)isEqual:(id)object {
return [self.objectId isEqualToString:[object objectId]];
}
#end
And the usage code:
NoteTemplate *nt1 = [[NoteTemplate alloc] initWithObjectId:#"1" text:#"old set"];
NoteTemplate *nt2 = [[NoteTemplate alloc] initWithObjectId:#"2" text:#"old set"];
NoteTemplate *nt3 = [[NoteTemplate alloc] initWithObjectId:#"1" text:#"new set"];
NoteTemplate *nt4 = [[NoteTemplate alloc] initWithObjectId:#"3" text:#"new set"];
NSSet <NoteTemplate *> *oldSet = [NSSet setWithObjects:nt1, nt2, nil];
NSSet <NoteTemplate *> *newSet = [NSSet setWithObjects:nt3, nt4, nil];
NSMutableSet <NoteTemplate *> *mergedSet = [newSet mutableCopy];
[mergedSet unionSet:oldSet];
for (NoteTemplate *note in mergedSet) {
NSLog(#"Set item %# %#", note.objectId, note.text);
}
After executing this code you'll see in the log:
Set item 3 new set
Set item 1 new set
Set item 2 old set
I assume that's what you were looking for.
I don't know if I'd call this elegant but it's a less brutish approach. Instead of filtering combined at every pass through the loop, get all the new IDs in advance and check the ID list in the loop.
NSMutableArray <NoteTemplate *> *combined = [NSMutableArray arrayWithArray:newTemplates];
NSArray <NSString *> *newTemplateIds = [newTemplates valueForKey:#"objectId"];
for (NoteTemplate *oldTemplate in oldTemplates) {
if (![newTemplateIds containsObject:oldTemplate.objectId]) {
[combined addObject:oldTemplate];
}
}

Can't write NSDictionary parameters into custom Object

I was stuck on writing NSDictionary into Object process, I am sure that problem is simple as I imagine but would be great to get assistant. Here is my code:
my custom object:
#interface User : NSObject
#property (nonatomic, retain) NSString *cId;
#property (nonatomic, retain) NSString *firstName;
#property (nonatomic, retain) NSString *lastName;
....
-(instancetype) initWithParameters:(NSDictionary*) parameters;
#end
#import "User.h"
#implementation User
-(instancetype) initWithParameters:(NSDictionary*) parameters
{
self = [super init];
if (self) {
[self setParameters:parameters];
}
return self;
}
- (void) setParameters:(NSDictionary*) parameters{
_cId = parameters[#"cId"];
_firstName = parameters[#"first_name"];
_lastName = parameters[#"last_name"];
....
}
and writing process:
id userObjects = [resultData objectForKey:#"data"];
NSMutableArray* mUsers = [[NSMutableArray alloc] init];
for (NSDictionary* userParameters in userObjects) {
User *user = [[User alloc] initWithParameters:userParameters];
[mUsers addObject:user];
}
userObjects - NSArray got from JSON object from server data.
The problem is : nothing happening and user object still empty after initialization, then I have tried - setValuesForKeysWithDictionary after I called variables same as keys in dictionary and nothing changed.
after adding in mUsers:
Could anybody tell me what I am doing wrong? Thank you!
I believe you think those objects are uninitialized because you are seeing 0 key/value pairs next to each User object.
Your code looks good and I think things will change once you implement [NSObject description] (or [NSObject debugDescription]) like this:
- (NSString *)description
{
return [NSString stringWithFormat:#"cId=%#, firstName=%#, lastName=%#",
_cId, _firstName, _lastName];
}

Unable to store a NSMutableArray(each instance of array contains a custom object) in NSUserDefaults

It's a custom class:
#import <Foundation/Foundation.h>
#interface timeTable : NSObject
#property (nonatomic) int ID;
#property (nonatomic) NSString * type;
#property (nonatomic) NSString * time;
#property (nonatomic) NSString * busno;
#property (nonatomic) NSString * stops;
// nothing is done in it's .m file not even synthesise
// thats an other class
#import <Foundation/Foundation.h>
#import "timeTable.h"
#interface refreshDatabase : NSObject
#property (strong, nonatomic) NSMutableArray * arrayTimeTable;
#property (strong, nonatomic) timeTable * objectTimeTable;
// in it's .m file i am downloading a JSON formatted array using a
service then i am saving it to NsMutbaleArray
// downloading a json array which contains a rows of data
NSError * error;
NSArray * jsonArray = [NSJSONSerialization JSONObjectWithData:
[safeString dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingAllowFragments error:&error];
NSLog(#"json Array %#", jsonArray);
// for getting an instance of array
NSDictionary * jsonElement;
for (int i=0; i<jsonArray.count ; i++)
{ // each row will be saved in an object of timetable class then that
// object will be saved to nsmutablearray
jsonElement = [jsonArray objectAtIndex:i];
objectTimeTable = [[timeTable alloc]init];
objectTimeTable.ID = [[jsonElement objectForKey:#"id"]intValue];
objectTimeTable.type = [jsonElement objectForKey:#"type"];
objectTimeTable.time = [jsonElement objectForKey:#"time"];
objectTimeTable.busno = [jsonElement objectForKey:#"busno"];
objectTimeTable.stops = [jsonElement objectForKey:#"stops"];
// adding an instance from JSON Array to our NSmutablearray
[arrayTimeTable addObject:objectTimeTable];
}//end of json Array FOR loop
// our array containnig all the objects will be saved using
//NSUserDefualts
// userDefaults is an object of NSUserDefaults
if(userDefaults)
{ // its not saving it to userdefaults
[userDefaults setObject:arrayToStore forKey:#"ArrayOfTimeTables"];
[userDefaults synchronize];
}
// retrieving the saved array from NSUSerDefaults and printing it
// using slog
timeTable *objPrint = [[timeTable alloc]init];
NSMutableArray *arrayLoader = [userDefaults arrayForKey:#"ArrayOfTimeTables"];
for (int i=0; i<arrayLoader.count ; i++)
{
objPrint = [arrayLoader objectAtIndex:i];
NSLog(#"outSide Printing For LOOP After Loading of tim # %d times havind id =%d type = %# time = %# busno = %# stops = %#",i,objPrint.ID,objPrint.type,objPrint.time,objPrint.busno,objPrint.stops);
}
Thanx a lot in helping me in advance.
Please tell me how to save that array which contains object of timetable class into nsUseDefaults and then how to load it back.
Please help me. I read a lot of similar question and answers, but don't know how to make them work for me.
Use NScoding to encode each of your custom object then add that custom object into an array then encode other and then add it to the array then save that array into NSUserDefaults
encoding and decoding of upper given question
is
the custom class .h file
#import <Foundation/Foundation.h>
#interface timeTable : NSObject<NSCoding>
#property (nonatomic) NSString * ID;
#property (nonatomic) NSString * type;
#property (nonatomic) NSString * time;
#property (nonatomic) NSString * busno;
#property (nonatomic) NSString * stops;
the custom class .m file
#import "timeTable.h"
#implementation timeTable
#synthesize ID;
#synthesize type;
#synthesize time;
#synthesize busno;
#synthesize stops;
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.ID forKey:#"ID"];
[aCoder encodeObject:self.type forKey:#"type"];
[aCoder encodeObject:self.time forKey:#"time"];
[aCoder encodeObject:self.busno forKey:#"busno"];
[aCoder encodeObject:self.stops forKey:#"stops"];
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
if((self = [super init])) {
//decode properties, other class vars
self.ID = [aDecoder decodeObjectForKey:#"ID"];
self.type = [aDecoder decodeObjectForKey:#"type"];
self.time = [aDecoder decodeObjectForKey:#"time"];
self.busno = [aDecoder decodeObjectForKey:#"busno"];
self.stops = [aDecoder decodeObjectForKey:#"stops"];
}
return self;
}
#end
where you encode each custom object one by one and adding it to the array then save that NSMutableArray or NSArray
into NSUserDefaults
encoding a custom object then adding it to array and saving it into user defaults
// encoding a custom object before saving it to array
NSData *encodeTimeTableObj = [NSKeyedArchiver
archivedDataWithRootObject:objectTimeTable];
addObject:encodeTimeTableObj];
//saving it to user Defaults
if(userDefaults)
{
[userDefaults setObject:arrayTimeTable
forKey:#"ArrayOfTimeTables"];
[userDefaults synchronize];
NSLog(#"saving to usedefaults");
}
retriving an array either mutable or non mutable then decoding each of its object
NSMutableArray *arrayLoader = [userDefaults
objectForKey:#"ArrayOfTimeTables"];
NSData * decode = [arrayLoader objectAtIndex:0];
  // in case of upper given custom class Time Table
timeTable *objPrint = [NSKeyedUnarchiver unarchiveObjectWithData:decode];
Use NSArray to get array from NSUSerDefaults as NSUSerDefaults return immuttable array.
If you need NSMutableArray, then convert this NSArray to NSMutableArray.
// retrieving the saved array from NSUSerDefaults and printing it
// using slog
timeTable *objPrint = [[timeTable alloc]init];
NSArray *arrayLoader = [userDefaults arrayForKey:#"ArrayOfTimeTables"];
for (int i=0; i

Save array of objects on the disc with JSONModel (IOS)

I read a lot of docs about this but I can't really understand how it precisely works.
I would like to save my apps data in JSON format on the disc of the phone.
I have a array of objects of this type:
#interface ObjectA : NSObject
#property (strong, nonatomic) NSMutableArray* names1;
#property (strong, nonatomic) NSMutableArray* names2;
#property (strong, nonatomic) NSMutableArray* names3;
#property (strong, nonatomic) NSMutableArray* names4;
#property (strong, nonatomic) NSString* nameObjectA;
#property (assign) int number;
By using JSONModel, how can I transforme a "NSMutableArray *ObjectA" in a JSON file and after that read this file back in the app.
Thanks.
- (id)initWithJSONDictionary:(NSDictionary *)jsonDictionary {
if(self = [self init]) {
// Assign all properties with keyed values from the dictionary
_nameObjectA = [jsonDictionary objectForKey:#"nameAction"];
_number = [[jsonDictionary objectForKey:#"number"]intValue];
_actions1 = [jsonDictionary objectForKey:#"Action1"];
_actions2 = [jsonDictionary objectForKey:#"Action2"];
_actions3 = [jsonDictionary objectForKey:#"Action3"];
_actions4 = [jsonDictionary objectForKey:#"Action4"];
}
return self;
}
- (NSArray *)locationsFromJSONFile:(NSURL *)url {
// Create a NSURLRequest with the given URL
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0];
// Get the data
NSURLResponse *response;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
// Now create a NSDictionary from the JSON data
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
// Create a new array to hold the locations
NSMutableArray *actions = [[NSMutableArray alloc] init];
// Get an array of dictionaries with the key "actions"
NSArray *array = [jsonDictionary objectForKey:#"actions"];
// Iterate through the array of dictionaries
for(NSDictionary *dict in array) {
// Create a new Location object for each one and initialise it with information in the dictionary
Action *action = [[Action alloc] initWithJSONDictionary:dict];
// Add the Location object to the array
[actions addObject:action];
}
// Return the array of actions objects
return actions;
}
The demo app that comes with JSONModel includes an example how to store your app's data via a JSONMOdel: https://github.com/icanzilb/JSONModel
Check the code in this view controller: https://github.com/icanzilb/JSONModel/blob/master/JSONModelDemo_iOS/StorageViewController.m
The logic is that you can export your model to a json string or json compliant dictionary and then save those to the disc using the standard APIs. Check the code
In ObjectA you define two methods -- toDictionary and initWithDictionary. Roughly:
-(NSDictionary*) toDictionary {
return #{#"names1":names1, #"names2":names2, #"names3":names3, #"names4":names4, #"nameObjectA":nameObjectA, #"number":#(number)};
}
- (id) initWithDictionary:(NSDictionary*) json {
self = [super init];
if (self) {
self.names1 = json[#"names1];
... etc
self.nameObjectA = json[#"nameObjectA"];
self.number = json[#"number"].intValue;
}
return self;
}
Run the dictionary created by toDictionary through NSJSONSerialization to produce an NSData and write that to a file. To read, fetch the NSData from the file, run back through NSJSONSerialization, and use initWithDictionary.
Of course, this assumes that the contents of your dictionaries are "JSON legal" -- strings, numbers, NSArrays, or other NSDictionarys.
And, if the arrays/dictionaries being initialized are mutable, one should specify the "MutableContainers" option on NSJSONSerialization.

ios NSDictionary always shows up empty

h
#import <UIKit/UIKit.h>
#interface LoginViewController : UIViewController <UITextFieldDelegate>
#property (nonatomic, retain) NSDictionary *_data;
#end
m
#import "LoginViewController.h"
#import "XMLReader.h"
#implementation LoginViewController
static NSDictionary *_raceInformation;
#synthesize _data, bibInput, lastNameInput, error;
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
NSError *e = [NSError alloc];
NSString *xml = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"testhalfmarathon" ofType:#"xml"] encoding:NSUTF8StringEncoding error:&e];
NSDictionary *asdf = [XMLReader dictionaryForXMLString:xml error:nil];
self._data = [XMLReader dictionaryForXMLString:xml error:nil];
//[xml release];
//[e release];
// !! BREAKPOINT HERE
}
return self;
}
When I hit the breakpoint, the value for self._data is nil. However, the value for asdf is the correct dictionary value I would expect in self._data. What gives?
backstory: I'm a n00b when it comes to MRC as I usually use ARC/GC languages.
What line of code did you put the breakpoint against? If it was just a blank line it will actually break at the previous valid line of code, which may have been before self._data was set.
Try putting NSLog(#"data %#", self._data); instead of your breakpoint and see what gets logged.
BTW, I see you had [xml release], which you commented out, presumably because it wasn't working. The reason this line is wrong is that [XMLReader dictionaryForXMLString...] returns an autoreleased object that shouldn't be released again.
In general, in Objective-C if a method name doesn't begin with "new", "alloc" or "copy" then it returns an autoreleased object that you don't need to release yourself.

Resources