I'm communicating with a server in Swift retrieving image data. The incoming data is encoded as a base64 string. I am able to correctly receive and display the encoded strings. When I go to use the NSData class to decode the string back to binary data and display...
println(NSData(base64EncodedString: imageString, options: NSDataBase64DecodingOptions(0)))
The output is
nil
nil
nil
nil
nil
nil
One for each of the images received.
I've also tried
println(NSData(base64EncodedString: imageString, options: nil))
and the same results. Is there anything I am missing along the way?? I would put the image strings up but they are massively long...
For others that may be having this issue, make sure your Base64 encoded string has a length divisible by 4 (= should be used to pad the length).
See this StackOverflow answer here: https://stackoverflow.com/a/36366421/330494
Try to use IgnoreUnknownCharacters option.
Or try to use initWithBase64EncodedString from NSDataAdditions
This can also happen if the input is so-called "URL Safe" Base64 data. This data has the + symbol replaced by the - symbol, and the / symbol replaced by the _ symbol.
Fortunately it's straightforward to convert it:
inputString = [[inputString stringByReplacingOccurrencesOfString:#"-" withString:#"+"] stringByReplacingOccurrencesOfString:#"_" withString:#"/"];
A full list of variants is available on Wikipedia.
Based on Frank Schmitt's and Barlow Tucker's answers I've created an extension to Data to better handle base64 encoding:
extension Data {
static func decodeUrlSafeBase64(_ value: String) throws -> Data {
var stringtoDecode: String = value.replacingOccurrences(of: "-", with: "+")
stringtoDecode = stringtoDecode.replacingOccurrences(of: "_", with: "/")
switch (stringtoDecode.utf8.count % 4) {
case 2:
stringtoDecode += "=="
case 3:
stringtoDecode += "="
default:
break
}
guard let data = Data(base64Encoded: stringtoDecode, options: [.ignoreUnknownCharacters]) else {
throw NSError(domain: "decodeUrlSafeBase64", code: 1,
userInfo: [NSLocalizedDescriptionKey: "Can't decode base64 string"])
}
return data
}
}
so in your code, you can use it like this:
let baseEncodeText = "(.....)" //your base64 encoded string
let data = try Data.decodeUrlSafeBase64(baseEncodeText)
Related
Can someone explain the behavior of the function below? Some have suggested to not use NSData. Do you have better alternatives to mention? If the returned value is Base64Encoded can I decode on one of the online encoders/decoders? Thanks.
func stringToData(message: String) -> NSData? {
let strData = NSData(base64Encoded: message, options: NSData.Base64DecodingOptions.ignoreUnknownCharacters)
return strData
}
NSData(base64Encoded:options:) is documented to attempt to initialize a data object with the given Base64 encoded string—and return nil if it fails. In other words; it decodes a Base64 encoded string as an NSData object.
In Swift, you would likely use the base64EncodedString() function and the Data(base64Encoded:) initializer on the Data type to encode and decode data as Base64 strings, for example like this:
let originalData = Data(bytes: [1,2,3,4,5,6,7,8,9,10,11,12])
let encodedAsBase64String = originalData.base64EncodedString()
// "AQIDBAUGBwgJCgsM"
let decodedData = Data(base64Encoded: encodedAsBase64String) // is optional because the decoding can fail
// 12 bytes: <01020304 05060708 090A0B0C>
I am working with some 3rd party data. The data they are returning from our request is suppose to be base64 encoded into a string. They provide these instructions for converting it to a readable format:
*First, converting the Base64 string to hexadecimals using the open-source "tomeko" online tool: http://tomeko.net/online_tools/base64.php?lang=en.
Second, in the SAE J2735 standard, all messages are sent as a MessageFrame. The MessageFrame contains information about which particular type of message is included within the MessageFrame. The proper way to decode the hexadecimals from step #1, is to select MessageFrame under the ASN.1 message in the open-source Marben online decoder tool: http://www.marben-products.com/asn.1/services/decoder-asn1-automotive.html.
*
I'm working on an iPhone app so reaching out to these sites for the conversion is not optimal. I decoded the B64 string into hex with this code:
func convert64EncodedToHex(_ data:Data) -> String {
return data.map{ String(format: "%02x", $0) }.joined()
}
which came back as so:
5B224142 4E464141 41414F51 6F414141 63414545 4E427055 476C5141 45434B67 796C6A4C 49414442 44515A2B 686E3641 4341686F 4D77517A 42414251 51304753 675A4B41 41774961 44536F4E 4B674163 454E426D 43475949 41514347 677A7544 4F34225D
But when I plug that into the Marben decoder it fails:
<error>
<description>Unexpected end of stream reached when decoding</description>
<nature>fr.marben.asnsdk.japi.InconsistentStructureException</nature>
<ErrorOffset>72</ErrorOffset>
<ValuePath>SPAT.intersections.IntersectionState#1.states.MovementState#1.state-time-speed.MovementEvent#1.regional.SEQUENCE#1.regExtValue</ValuePath>
</error>
Eventually I would need to decode the hex into readable string in the app so I was wondering:
Why my b64 to hex code seems to be failing
how to transform hex to readable string
EDIT
OK, on the Marben site I was selecting the wrong drop down for decoding. Selecting MessageFrame provided a successful result so the b64 to hex I have is working. New problem arose from that is I can't decipher these results:
68 bytes decoded.
* DECODING SUCCESSFUL *
A couple of problems.
The hex string you provided translates to an original response of:
["ABNFAAAAOQoAAAcAEENBpUGlQAECKgyljLIADBDQZ+hn6ACAhoMwQzBABQQ0GSgZKAAwIaDSoNKgAcENBmCGYIAQCGgzuDO4"]
Perhaps they sent it to you in a JSON array? Bottom line, you need to trim out the [" and "] before you do anything else with this. For example, if it was JSON, you could parse the JSON before you base64-decode it:
do {
let array = try JSONDecoder().decode([String].self, from: originalJSONData)
if let base64String = array.first {
// use `base64String` here
}
} catch {
print(error)
}
If you want the data associated with that base64 string, you'd do something like:
let base64String = "ABNFAAAAOQoAAAcAEENBpUGlQAECKgyljLIADBDQZ+hn6ACAhoMwQzBABQQ0GSgZKAAwIaDSoNKgAcENBmCGYIAQCGgzuDO4"
let payload = Data(base64Encoded: base64String)!
If you merely want to display that as a hex string (I'm not sure why you'd want to do that), the easiest way is:
print(payload as NSData)
That would display
<00134500 0000390a 00000700 104341a5 41a54001 022a0ca5 8cb2000c 10d067e8 67e80080 86833043 30400504 34192819 28003021 a0d2a0d2 a001c10d 06608660 80100868 33b833b8>
If you really need that hex String representation, you can use the routine you have in your question (but do it on the base64-decoded payload, rather than the base64 string):
let payloadString = payload.map { String(format: "%02x", $0) }
.joined()
print(payloadString)
That will return:
001345000000390a00000700104341a541a54001022a0ca58cb2000c10d067e867e8008086833043304005043419281928003021a0d2a0d2a001c10d066086608010086833b833b8
Personally, the hex strings (in step 3 and 4) are interesting if you want to visually examine the binary payload, but probably you really need the original payload (in step 2). As I look at this data, it's not immediately obvious what this "3rd party data" returned, so I cannot comment further on that, but you presumably have some mechanism to consume this payload.
Converting Data to String returns a nil value.
Code:
// thus unwraps the image
if let image = image{
print("Saving image data")
// don't unwrap here
if let data = UIImagePNGRepresentation(image){
let str = String(data: data, encoding: .utf8)
print(str)
}
}
I don't know the reason.
Also, how do I convert the String back to Data?
This doesn't work because when you interpret the bytes of the Image as a String, the string is invalid. Not every jumble of data is a valid utf8 string. i.e. not every collection of n bits (8, sometimes 16) are a valid utf8 code point. The Swift String api loops through the data object you pass it to validate that it is a valid string. In your case, theres no reason to think that this Data is a valid string, so it doesn't work.
A good read on utf8:
https://www.objc.io/issues/9-strings/unicode/
When I got JSON {"categories": "[\"a/b/c\",\"d\"]"} from server,
Then How can I parse it to ["a/b/c", "d"]
When I using .arrayValue, then it always return []
You have an array represented as raw JSON string within your JSON. You have three options:
Try to manually scan through replacing occurrences of \" (which you'll have to confusingly escape both the backslash character and the quotation mark encountered with a backslash when you write your code, e.g. "\\\"") before you parse the JSON. To do this, you'd probably use responseData rather than responseJSON, do your manual replacement of characters, and then manually parse it yourself with NSJSONSerialization. The downside here is that you might want to check for other escaped characters, too (e.g. what if the nested JSON was pretty printed, then you might have \n or even \t in there, too, which you'd want to convert as well).
Manually run JSON parsing again for each of those values that contain a JSON string in the responseObject. If you're stuck with the existing JSON, I think this is probably safest.
For example, if the raw json was really just:
{"categories": "[\"a/b/c\",\"d\"]"}
Then you could do something like:
Alamofire.request(request)
.responseJSON { response in
guard response.result.error == nil else {
print(response.result.error!)
return
}
do {
if let dictionary = response.result.value as? [String: String],
let categoryData = dictionary["categories"]?.dataUsingEncoding(NSUTF8StringEncoding),
let categories = try NSJSONSerialization.JSONObjectWithData(categoryData, options: []) as? [String]
{
let result = ["categories" : categories]
print(result)
}
} catch {
print(error)
}
}
Then the result would be:
["categories": ["a/b/c", "d"]]
Fix the web service that's generating this JSON so that it's not doing this silliness. This is probably the best solution, though it will take some detective work to figure out why/how the web service put JSON inside JSON. I know that you say you can't change the server side, but you really should escalate this to someone on the server team to fix this. It's silly to write client code to work around some mistake (either a mistake in the server code itself, or how the server was provided the data in the first place).
First up, the response you're getting from the server is a String and not Array. Hence you'll get empty array when you do .arrayValue . If you want to convert the string into an array, first you need to remove the "[" & "]" and then you need to do the componentsSeparatedByString with string as "," for the resultant string. You can use the following code snippet to do that:
let str = "[\"a/b/c\",\"d\"]"
let filteredString = String (str.characters.filter { $0 != "[" && $0 != "]"})
print(filteredString)
let filteredStringArray = filteredString.componentsSeparatedByString(",")
print(filteredStringArray)
HTH :)
I am new to JSON. Are there any methods in JSON parser to remove the comment characters from a response.
Eg. //{"response":"success".......
its SBJson for iPhone.
from http://code.google.com/p/json-framework
The JSON grammar doesn't allow comments. That doesn't answer your question obviously, but I suspect you'll have to do some string manipulation and replace all those comment characters with empty strings and parse it with the JSON library only after doing so.
Nowadays, very easy to do this:
Here's how you get and parse actual json
// normal json no comments
func getStuff() {
guard let url = URL(string: "http://you.com/x.json") else { return print("?") }
let configuration = URLSessionConfiguration.ephemeral
aSession = URLSession(configuration: configuration)
aSession.dataTask(with: url) { [weak self] data, response, error in
guard let _ = self else { return print("woe") }
guard let data = data else { return print("woe") }
do {
let result = try JSONDecoder().decode(YourStructure.self, from: data)
localBlah = Dictionary(uniqueKeysWithValues: result.whatever)
} catch let error {
print(error)
}
}.resume()
}
Here's how you get and parse "json" which has simple comment lines:
During development, remove #comment lines from "json":
Notice the line of code which decodes the data :
let result = try JSONDecoder().decode(YourStructure.self, from: data)
Simply paste in these three lines of code, before, that line:
let s = String(decoding: data, as: UTF8.self)
let fixed = s.replacingOccurrences(
of: "(?m)^#.*",
with: "",
options: .regularExpression)
guard let data2: Data = fixed.data(using: .utf8) else { return print("woe") }
let result = try JSONDecoder().decode(YourStructure.self, from: data2)
So during development in your "json" on your server, you can have things like ..
"measures": [{
"screen": "options",
"topMargin": 25,
#don 't change topmargin anyone
"leftMargin": 12,
#Note,
Steve prefers 13. But everyone
else prefers 12. "rightMargin": 20,
},
It's that simple.
Important note on using regex:
Regex is a sophisticated process. The example regex used in this post simply means
"Delete any full lines, which, start with a '#'
So, it only understands simple "full-line" comments.
How to write regex is beyond the scope of this QA.
When you pre-munge the text at let fixed =, use whatever regex or other technique you wish.
The JSON parsers are very finicky about what is at the start of a JSON block to parse - they DO NOT like characters other than "{" at the start (at least that's what I found with TouchJSON, and it sounds like your case with SBJson is similar).
So just take your string and eliminate any characters before the opening "{", then you can parse:
NSRange startJSONRange = [myJSONString rangeOfString:#"{"];
startJSONRange.length = myJSONString.length - startJSONRange.location;
NSString *correctJSONString = [myJSONString substringWithRange:startJSONRange];
// parse correctJSONString
That will work, but the REAL fix is to tell whoever is sending you JSON to cut out the nonsense and send real JSON.