Xcode swift download binary file from json - ios

I have json rpc service, one method gives binary file with metadata, for example:
{
id = 1;
jsonrpc = "2.0";
result = {
anonymized = 0;
id = 331210;
logged = 1;
content = "e1xydGYxXGFkZWZsYW5nM… …AwMDAwMDAwMDAwMH19";
};
}
On client side I can correctly deserialize response by:
let responseObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parseError) as? NSDictionary
I can read object result as NSDictionary, but can not find way to save field content to disk as binary file. I did not find any solution.
Thanks for your help.

Ok, so content looks like it's base64 encoded. If so, you would do it like this:
if let encodedData = responseObject?["result"]?["content"] as? String {
if let data = NSData(base64EncodedString: encodedData, options: nil) {
data.writeToFile(desiredFilePath, atomically: true)
}
}

Related

Apple in app provisioning 'Could not add card'

I am implementing apple in-app provisioning and I follow all steps in the apple guide but in the end, I get a message 'Could not add card' but don't have any error throw this process.
This is how I create PKAddPaymentPassViewController
let cardInfoPass = PKAddPaymentPassRequestConfiguration.init(encryptionScheme: PKEncryptionScheme.ECC_V2);
cardInfoPass?.cardholderName = cardholderName as? String; //The name of the person as shown on the card.
cardInfoPass?.primaryAccountSuffix = primaryAccountSuffix as? String; //The last four or five digits of the card’s number.
cardInfoPass?.localizedDescription = localizedDescription as? String; //A short description of the card.
cardInfoPass?.paymentNetwork = PKPaymentNetwork.masterCard;
cardInfoPass?.primaryAccountIdentifier = primaryAccountIdentifier as? String; // A primary account identifier, used to filter out pass libraries.
cardholderName is the name written on the card
primaryAccountSuffix last 4 digit written on the card
localizedDescription bank name
paymentNetwork we are using master card
primaryAccountIdentifier it is number from iTunes something light this 1MNJDDA667.com.bank.package.name
I think this part is correct I can open the apple wallet modal and all this data are there but when I continue in a modal on the end I need to get certificate and send this certificate to our BE and be should send me back 3 values and they send it to me
...
let certificateLeaf = certificates[0].base64EncodedString();
let certificateSubCA = certificates[1].base64EncodedString();
let nonceString = nonce.base64EncodedString();
let nonceSignature = nonceSignature.base64EncodedString();
...
let reqDataDic: [String: Any] = [
"cardId": cardId,
"applePublicCertificate": certificateSubCA,
"nonce": nonceString,
"nonceSignature": nonceSignature,
"customerId": customerId,
"deviceId": deviceId,
]
....
var request = URLRequest(url: url)
request.httpMethod = "POST"
....
request.httpBody = try? JSONSerialization.data(withJSONObject: reqDataDic, options: .prettyPrinted)
UPDATE2: we are now sending nonce and nonceSignature as HEX like this
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
return self.map { String(format: format, $0) }.joined()
}
}
...
let nonceData = Data(bytes: nonce)
let nonceHex = nonceData.hexEncodedString();
let nonceSignatureData = Data(bytes: nonceSignature)
let nonceSignatureHex = nonceSignatureData.hexEncodedString();
BE send me back all values that I need: activationData, ephemeralPublicKey, encryptedPassData it returns it as a JSON object so I need to convert it to Data and all these values put into handler
this is how I am putting data to handler:
if let dictionaryJson = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any] {
let activationDataString = dictionaryJson["activationData"] as! String;
let ephemeralPublicKeyString = dictionaryJson["ephemeralPublicKey"] as! String;
let encryptedPassDataString = dictionaryJson["encryptedPassData"] as! String;
let activationData = activationDataString.data(using: .utf8)
let ephemeralPublicKey = Data(base64Encoded: ephemeralPublicKeyString)
let encryptedPassData = Data(base64Encoded: encryptedPassDataString)
let paymentPassRequest = PKAddPaymentPassRequest.init()
paymentPassRequest.activationData = activationData;
paymentPassRequest.encryptedPassData = encryptedPassData;
paymentPassRequest.ephemeralPublicKey = ephemeralPublicKey;
handler(paymentPassRequest)
}
I fill all data into paymentPassRequest and all looks ok xCode is not complaining.
And at this moment apple wallet shows an alert dialog with Could not add a card with 2 buttons try it later or try it again ....
I have a card whitelisted on the MasterCard side
I tried it on simulators, real devices, and also on app in TestFlight
UPDATE:
We found an error from the Apple
Response:
https://nc-pod4-smp-device.apple.com:443/broker/v4/devices/042D1xxxxxxxxxxxxx2C52/cards 500
{
Connection = close;
"Content-Length" = 81;
"Content-Type" = "application/json";
Date = "Thu, 08 Jul 2021 08:35:25 GMT";
Vary = "accept-language";
"X-Pod" = "nc-pod4";
"X-Pod-Region" = "paymentpass.com.apple";
"x-conversation-id" = b2axxxxxxxxxxx9e6a4d;
}
{
statusCode = 500;
statusMessage = "Broker Service Response exception";
}
you are encoding nonce, nonce signature with Hex format for sending it to your server, and after getting the response back, you are trying to convert them with base64 and utf8. Try with Hex, it should work.
We are using the below conversions
- (NSData *)dataFromHexString:(NSString *)string
{
string = [string lowercaseString];
NSMutableData *data= [NSMutableData new];
unsigned char whole_byte;
char byte_chars[3] = {'\0','\0','\0'};
int i = 0;
int length = string.length;
while (i < length-1) {
char c = [string characterAtIndex:i++];
if (c < '0' || (c > '9' && c < 'a') || c > 'f')
continue;
byte_chars[0] = c;
byte_chars[1] = [string characterAtIndex:i++];
whole_byte = strtol(byte_chars, NULL, 16);
[data appendBytes:&whole_byte length:1];
}
return data;
}
-(NSMutableString *) convertToString:(NSData *)data{
NSUInteger capacity = data.length * 2;
NSMutableString *sbuf = [NSMutableString stringWithCapacity:capacity];
const unsigned char *buf = data.bytes;
NSInteger i;
for (i=0; i<data.length; ++i) {
[sbuf appendFormat:#"%02x", (NSUInteger)buf[i]];
}
return sbuf;
}
let activationData = activationDataString.data(using: .utf8)
I think this encoded is only for VISA.
For MasterCard it has to be base64:
let activationData = Data(base64Encoded: activationDataString)
I ran into this exact issue implementing our Card Provisioning. In our case we had to do both of the following:
Make sure our PKAddPaymentPassRequest fields were all set properly. All three of the fields we were provided by our API (activationData, encryptedPassData, ephemeralPublicKey) were base64 encoded so they all had to be converted to Data's as such: paymentPassRequest.ephemeralPublicKey = Data(base64Encoded: <YOUR EPHEMERAL PUBLIC KEY STRING>, options: [])
We had to create new TestFlight builds in order to fully test this workflow. I ran into that exact same 500 response from Apple anytime I tried profiling or running the app from Xcode directly, even if it was on a physical device. It wasn't until I ran the TestFlight build that it finally worked properly.

How Fix Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 52."

How to Convert this one. "{\n ID = \"d9a7c7bf-781d-47b3-bb4e-e1022ec4ce1b\";\n Name = Headquarters;\n}"; To this format {
"ID": "d9a7c7bf-781d-47b3-bb4e-e1022ec4ce1b",
"Name": "Headquarters"
}
if let jsonString = text as? String {
let objectData = jsonString.data(using: String.Encoding.utf8)
do {
let json = try JSONSerialization.jsonObject(with: objectData!, options: .allowFragments) as! [String:Any] //try JSONSerialization.jsonObject(with: objectData!, options: JSONSerialization.ReadingOptions.mutableContainers)
print(String(describing: json))
return json
} catch {
// Handle error
print(error)
}
}
Blockquote
First of all and already mentioned the string format is clearly not JSON.
It's the string format which is returned when calling the description property of a Foundation collection type (NSArray / NSDictionary).
For example a print statement calls description and this format appears also in output of Terminal.app.
However there is a solution: This string format is called openStep (an OpenStep / NeXt legacy format) and is available in PropertyListSerialization
This code reads the format:
let string = "{\n ID = \"d9a7c7bf-781d-47b3-bb4e-e1022ec4ce1b\";\n Name = Headquarters;\n}"
let data = Data(string.utf8)
do {
let dictionary = try PropertyListSerialization.propertyList(from: data, format: nil)
print(dictionary)
} catch { print(error) }
Note:
I'm pretty sure that the original data format is not openStep and somewhere you created the string unnecessarily with the String(describing initializer like in the question.
your json format is incorrect. If you try it with jsonformatter it will throw this error:
so first you need to replace ; with ,. The second is that Strings should be wrapped in double quotes, replace Name = Headquarters with Name = "Headquarters".
This is the right form
{\n ID = \"d9a7c7bf-781d-47b3-bb4e-e1022ec4ce1b\",
\n Name = "Headquarters"\n}

Get value of json response swift

This is my code:
do {
if let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary {
print(json)
let success = json ["success"] as? Int
print("Success: \(success)")
And this is my output:
{
error = "Account does not exist, please create it!";
}
Success: nil
`
So, before let success = json ["success"] as? Int, everything works well, but why is my output after this line nil?
This is my php:
public function login($username,$password) {
$query = "Select * from users where username = '$username' and password = '$password'";
$result = mysqli_query($this->connection, $query);
if (mysqli_num_rows($result) > 0) {
$json['success'] = 'Welcome '. $username . '!';
echo json_encode($json);
mysqli_close($this->connection);
} else {
$json['error'] = 'Account does not exist, please create it!';
echo json_encode($json);
mysqli_close($this->connection);
}
let success = json ["success"] as? Int
When you use this line it will extract the value of the key "success". As your json response does not contain that field it sets nil in the success variable.
Along with the error key you will need to return the success key too.
Success is nil because key 'success' does not exist in the JSON.
X as? Int = try to make x an Int from X when possible. If not possible (because the value is nil or the value is not convertible to an Int), make it Nil. That's what the question mark does.
So, I would do this:
if let success = json ["success"] as? Int {
print("Success: \(success)")
} else {
// Failed
}
You could also change your PHP code to make sure it always returns the 'success' key. However, I would recommend to use the Swift code above since you are always safe then.

count occurrences of a key in JSON data

I want to count number of "items" in my json result for me to loop on it. Please see the code below which doesn't work for me:
var jsonResult:NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as NSDictionary
for var i = 0; i < jsonResult["items"].count; i++ {}
It gives me following compile error:
"AnyObject? doesnt have a member named count"
Can you please help me understand the reason behind it? and also,
Can you please let me know the work around it?
Thanks for your help
Because you're parsing dynamic data, the compiler can't make guarantees about what the JSON parser returns at runtime. For that reason, any value you retrieve from a JSON object is an optional you have to explicitly unwrap.
You can read about it at http://www.atimi.com/simple-json-parsing-swift-2/.
I believe there are libraries for JSON parsing in Swift now, that make this type of stuff a lot easier.
Because jsonResult["items"] this returnAnyObject? ,you have to convert it to real class it is.For example
var dic:NSMutableDictionary = NSMutableDictionary()
dic.setValue(["first","second","third"], forKey: "items")
var json:NSData = NSJSONSerialization.dataWithJSONObject(dic, options: NSJSONWritingOptions.PrettyPrinted, error: nil)!
var jsondic = NSJSONSerialization.JSONObjectWithData(json, options:NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary
var jsonItems = jsondic["items"] as NSArray
for var i = 0;i < jsonItems.count; i++ {
println(jsonItems[i] as String)
}
This is the key
var jsonItems = jsondic["items"] as NSArray
You may use "as?",so that if convert fail,you app will not crash
var jsonItems = jsondic["items"] as? NSArray
if(jsonItems != nil){
for var i = 0;i < jsonItems!.count; i++ {
println(jsonItems![i] as String)
}
}

RestKit relationships not mapping when manually mapping JSON object

I am following some suggested posts on how to map incoming JSON data directly using RKMapperOperation. My entity object is being created in the data store, but without the proper relationships. Interestingly, if I create an object directly using the Core Data methods after I've already mapping incoming JSON (via websockets), the operation seems to "flesh" out my relationships in the incorrect entity.
To sum the order:
JSON data comes into app through a websocket connection
I map it using the below code, but the relationships aren't mapped
I save some other record in the same entity using a locally created (not RestKit) object with Core Data.
My object mapped from JSON now has its relationships attached!
Here is the JSON data:
{
"checkin": {
"session_id": 1,
"attendee_id": 70,
"source": "list",
"created_at": "2015-03-26 11:53:08",
"cache_id": "9234d700852df5c7402b87adf6ecfc19",
"checkout": "0",
"updated_at": "2015-03-27 03:53:09",
"id": 359
}
}
Here is my mapping function
func mapEntityFromJson(JSONString: String, key: String, mapping: RKEntityMapping!) -> NSManagedObject? {
let MIMEType = "application/json"
var error: NSError? = nil
let data = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let jsonDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
let parsedData: AnyObject! = RKMIMETypeSerialization.objectFromData(data, MIMEType: MIMEType, error: &error)
if (parsedData == nil && error != nil) {
// Parser error...
return nil
}
let mappingsDictionary = [ key: mapping ]
let mappingDS = RKManagedObjectMappingOperationDataSource(managedObjectContext: self.objectStore.persistentStoreManagedObjectContext, cache: self.objectStore.managedObjectCache)
let mapper = RKMapperOperation(representation: parsedData, mappingsDictionary: mappingsDictionary)
mapper.mappingOperationDataSource = mappingDS
var mappingError: NSError? = nil
let isMapped = mapper.execute(&mappingError)
if (isMapped && mappingError == nil) {
// Trying to save twice per some other example
self.objectStore.persistentStoreManagedObjectContext.save(&error)
self.objectStore.persistentStoreManagedObjectContext.saveToPersistentStore(&error)
let result = mapper.mappingResult.firstObject() as NSManagedObject
return result
}
return nil
}
Here is the relationship mapping I'm passing to this function:
func checkinMapping() -> RKEntityMapping {
let checkinMapping = RKEntityMapping(forEntityForName: "Checkin", inManagedObjectStore: objectStore)
let checkinDictionary = ["source": "source", "checkout": "checkout", "cache_id": "cacheId", "attendee_id": "attendeeId", "session_id": "sessionId"]
checkinMapping.addAttributeMappingsFromDictionary(baseRecordDictionary)
checkinMapping.addAttributeMappingsFromDictionary(checkinDictionary)
checkinMapping.addConnectionForRelationship("attendee", connectedBy: ["attendeeId": "id"])
checkinMapping.addConnectionForRelationship("session", connectedBy: ["sessionId": "id"])
checkinMapping.identificationAttributes = ["cacheId"]
checkinMapping.setDefaultValueForMissingAttributes = true
return checkinMapping
}
Here is how the function gets called when the websocket subscription is notified:
let jsonString = "<the JSON data per above>"
let mappingResult = self.mapEntityFromJson(jsonString, key: "checkin", mapping: self.checkinMapping())
The attendee_id and session_id values should be establishing a relationship with Attendee and Session entities, but when I look at the sqlite data underlying Core Data the relationship columns are blank even though the incoming attendeeId and sessionId fields get mapped. Once I make the other save locally then those relationship columns get mapped.
Any thoughts?
EDIT:
I should add that when a checkin is mapped from a full RestKit call like .getObject or as the result of .postObject then the mapping works with no issues. It is only in the manual mapping I have here that it seems to fall apart.
Here is the final solution which involved much help from #wain and a key point from another post about needing to make the operationQueue wait until all operations are finished or else the mapping process hadn't finished mapping the relationships. So my NSManagedObject was returning with no relationships connected. The combination of a private child context for proper concurrency and saving of data along with the waiting issue fixed the problem. Here is the final mapping code:
func mapEntityFromJson(JSONString: String, key: String, mapping: RKEntityMapping!) -> NSManagedObject? {
let MIMEType = "application/json"
var error: NSError? = nil
let data = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let jsonDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &error) as NSDictionary
let parsedData: AnyObject! = RKMIMETypeSerialization.objectFromData(data, MIMEType: MIMEType, error: &error)
if (parsedData == nil && error != nil) {
// Parser error...
return nil
}
let mapperContext = self.objectStore.newChildManagedObjectContextWithConcurrencyType(NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType, tracksChanges: true)
let mappingsDictionary = [ key: mapping ]
let mappingDS = RKManagedObjectMappingOperationDataSource(managedObjectContext: mapperContext, cache: self.objectStore.managedObjectCache)
var mappingResult: RKMappingResult? = nil
var result: NSManagedObject? = nil
let mapper = RKMapperOperation(representation: parsedData, mappingsDictionary: mappingsDictionary)
mappingDS.operationQueue = self.operationQueue
mappingDS.parentOperation = mapper
mapper.mappingOperationDataSource = mappingDS
var mappingError: NSError? = nil
let isMapped = mapper.execute(&mappingError)
// Necessary to wait for relationships to map.
if self.operationQueue!.operationCount > 0 {
self.operationQueue!.waitUntilAllOperationsAreFinished()
}
if (isMapped && mappingError == nil) {
mapperContext.saveToPersistentStore(&error)
mappingResult = mapper.mappingResult
}
if mappingResult != nil {
result = mappingResult!.firstObject() as? NSManagedObject
}
return result
}
For now we're leaving it with the in-memory cache and don't see the duplicates issue others reported.

Resources