Create a JSON String in Swift - ios

I send the text from a TextView to my Backend. To include linebreaks, i try to serialize the textView.text to JSON.
let jsonObject: NSMutableDictionary = NSMutableDictionary()
jsonObject.setValue(textView.text, forKey: "text")
let jsonData: NSData
do {
jsonData = try JSONSerialization.data(withJSONObject: jsonObject, options: JSONSerialization.WritingOptions()) as NSData
let jsonString = NSString(data: jsonData as Data, encoding: String.Encoding.utf8.rawValue) as! String
print(jsonString)
} catch _ {
print ("JSON Failure")
}
}
But since this is a Dictionary, the resulting text looks like this:
{"text":"test\nstack\n\noverflow"}
i actually just need: test\nstack\n\noverflow
Is there a pretty way to transform a multiline String in swift to have these "\n" for linebreaks without any extra characters?
Edit:
I expect to type into my textView something like this:
and as a result get the string "hello\n\n"
The source looks like this:
let text = textView.text
let components = text?.components(separatedBy: CharacterSet.newlines).filter({!$0.isEmpty})
let textWithLineFeeds = components?.joined(separator:"\n")
print(textWithLineFeeds!)
print(textView.text)
the first print gives:
test
hello
linebreak above
the second print statement:
test
hello
linebreak above
i wish i could see:
test\nhello\n\nlinebreak above

To convert text with arbitrary new line characters (CR, LF, CRLF etc.) to distinct - only LF - line breaks use:
let text = "test\r\nstack\n\noverflow\rfoo"
let components = text.components(separatedBy: CharacterSet.newlines).filter({!$0.isEmpty})
let textWithLineFeeds = components.joined(separator:"\n")
If the server accepts only CR, change \n to \r.

Related

Get array of tuples from txt file

I've txt file with content:
("All our dreams can come true, if we have the courage to pursue them.","Walt Disney")
("The secret of getting ahead is getting started","Mark Twain")
I want to get array of tuples from it with type [(String, String)]. I try to use code:
do {
if let path = Bundle.main.path(forResource: "quotes", ofType: "txt"){
let data = try String(contentsOfFile: path, encoding: .utf8)
let arrayOfStrings = data.components(separatedBy: "\n")
print(arrayOfStrings[0])
}
} catch let err as NSError {
// do something with Error
print(err)
}
But with it I cannot get tuple values. How I can get array of tuples from txt file with Swift?
As already mentioned in comments by Larme it would be better to properly format your text. If you can't change the text format you woill need to manually parse its contents:
let data = """
("All our dreams can come true, if we have the courage to pursue them.","Walt Disney")
("The secret of getting ahead is getting started","Mark Twain")
"""
let tuples = data.split(whereSeparator: \.isNewline)
.compactMap { line -> (Substring,Substring)? in
let comps = line.components(separatedBy: #"",""#)
guard comps.count == 2,
let lhs = comps.first?.dropFirst(2),
let rhs = comps.last?.dropLast(2) else { return nil }
return (lhs,rhs)
}
for tuple in tuples {
print(tuple.0)
print(tuple.1)
}
This will print:
All our dreams can come true, if we have the courage to pursue them.
Walt Disney
The secret of getting ahead is getting started
Mark Twain

Swift Array to String

There is an array like:
let datas =
[["#A1CCE4","", "+0.0%", "+0.0%"],
["#4C3C2F","G", "+1.0%", "+0.2%"],
["#4C3C2F","G", "+3.1%", "+0.6%"],
["#C07155","S", "+0.3%", "+0.1%"],
["#C07155","G", "+2.0%", "+0.4%"],
["#C07155","P", "+1.8%", "+0.3%"],
["#AEB0B3","R", "+2.0%", "+2.0%"]]
How to convert it to string like:
"""let data = [["#A1CCE4", "", "+0.0%", "+0.0%"], ["#4C3C2F", "G", "+1.0%", "+0.2%"], ["#4C3C2F", "G", "+3.1%", "+0.6%"], ["#C07155", "S", "+0.3%", "+0.1%"], ["#C07155", "G", "+2.0%", "+0.4%"], ["#C07155", "P", "+1.8%", "+0.3%"], ["#AEB0B3", "R", "+2.0%", "+2.0%"]]"""
(with "let data = " or not is fine)
EDIT:
"\(datas)" should be the answer.
or
"\(String(describing: datas))"
or
datas.description
Why do I ask this question?
The array actually from the plist, and the number values are from different places.
The number values need to be load in the js code in WKWebView. And acutually the js code is just string.
In my js code I have actually the same code datas variable code.
You actually want to create a JSON string, it's pretty easy with JSONEncoder because [String] conforms to Encodable by default.
let datas =
[["#A1CCE4","", "+0.0%", "+0.0%"],
["#4C3C2F","G", "+1.0%", "+0.2%"],
["#4C3C2F","G", "+3.1%", "+0.6%"],
["#C07155","S", "+0.3%", "+0.1%"],
["#C07155","G", "+2.0%", "+0.4%"],
["#C07155","P", "+1.8%", "+0.3%"],
["#AEB0B3","R", "+2.0%", "+2.0%"]]
do {
let jsonData = try JSONEncoder().encode(datas)
let result = String(data: jsonData, encoding: .utf8)!
print(result)
} catch { print(error) }
It's more reliable than just calling .description on the array.
And only an array literal is directly interchangeable with JSON, a dictionary is not.
If you really need the let datas = prefix just concatenate the strings
let resultWithPrefix = "let datas = " + result
Given your datas array, you can achieve your goal this way:
let datas =
[["#A1CCE4","", "+0.0%", "+0.0%"],
["#4C3C2F","G", "+1.0%", "+0.2%"],
["#4C3C2F","G", "+3.1%", "+0.6%"],
["#C07155","S", "+0.3%", "+0.1%"],
["#C07155","G", "+2.0%", "+0.4%"],
["#C07155","P", "+1.8%", "+0.3%"],
["#AEB0B3","R", "+2.0%", "+2.0%"]]
var str = " let datas = ["
for data in datas{
str += "\(data)"
}
str += "]"
EDIT:
You can use this code, with a loop for each String array in your data, to get a string with comma
var str = " let datas = ["
for data in datas{
str += "["
for s in data{
str += " \" \(s) \" , "
}
str += "],"
}
str += "]"
Another good solution is to use JSONEncoder, as suggested in other answer, I just report it here to complete
let datas = [...]
var myString = "let datas ="
do{
let datasToJson = JSONEncoder().encode(datas)
myString += String(data: datasToJson, encoding: .utf8)!
}catch{
print(error)
}
How about something like this:
let stringStart = "let datas = "
let stringDatas = String(datas)
let finalString = stringStart + stringDatas
More answers here

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}

How to convert special characters in dictionary to unicode in swift

//temporary dictionary
let tempDict = ["ttTasks":"€ is euro symbol"]
//conversion should replace € symbol with \u20ac
so final dictionary would be
finalDict = ["ttTasks":"\u20ac is euro symbol"]
NOTE: please do not suggest me replaceOccurencesOfString as i need to do this for every special character.
Something like this maybe:
for key in tempDict.keys {
let data = tempDict[key]?.data(using: String.Encoding.nonLossyASCII)
tempDict[key] = String(data: data!, encoding: String.Encoding.utf8)
}

Fast way to trim lines at the beginning of a log file on iOS

I'm looking for a fast, optimized way to trim log files on iOS. I want to specify that my log files have a maximum number of lines (e.g., 10,000). Appending new lines to the end of a text file seems relatively simple. However, I haven't yet found a fast way to trim lines at the beginning of the file. Here's the (slow) code I came up with.
guard let fileURL = self.fileURL else {
return
}
guard let path = fileURL.path else {
return
}
guard let fileHandle = NSFileHandle(forUpdatingAtPath: path) else {
return
}
fileHandle.seekToEndOfFile()
fileHandle.writeData(message.dataUsingEncoding(NSUTF8StringEncoding)!)
fileHandle.writeData("\n".dataUsingEncoding(NSUTF8StringEncoding)!)
currentLineCount += 1
// TODO: This could probably use some major optimization
if currentLineCount >= maxLineCount {
if let fileString = try? NSString(contentsOfURL: fileURL, encoding: NSUTF8StringEncoding) {
var lines = fileString.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
lines.removeFirst()
let newData = lines.joinWithSeparator("\n")
fileHandle.seekToFileOffset(0)
fileHandle.writeData(newData.dataUsingEncoding(NSUTF8StringEncoding)!)
}
}
fileHandle.closeFile()
There are two aspects of your question. First, your code removes a single
line from the log file. Therefore, once the limit is reached, every new
log message causes the entire file to be read, shortened, and be re-written.
It would be more effective to use a "high-water mark" and a "low-water mark". For example, if you want the last 10.000 lines to be preserved,
let the log file grow until it has 15.000 lines, and only then truncate
it to 10.000 lines. This reduces the number of "trim actions"
considerably.
The second part is about the truncating itself. Your code loads the
file into an NSString, which requires the conversion of UTF-8 data
to Unicode characters
(and fails if there is a single invalid byte in the log file).
Then the string is split into an array, one array element removed,
the array concatenated to a string again, and then written back
to the file, which converts the Unicode characters to UTF-8.
I haven't done performance tests, but I can imagine that it could be
faster to operate on binary data only, without the conversions to
NSString, Array and back. Here is a possible implementation
which removes a given number of lines from the start of a file:
func removeLinesFromFile(fileURL: NSURL, numLines: Int) {
do {
let data = try NSData(contentsOfURL: fileURL, options: .DataReadingMappedIfSafe)
let nl = "\n".dataUsingEncoding(NSUTF8StringEncoding)!
var lineNo = 0
var pos = 0
while lineNo < numLines {
// Find next newline character:
let range = data.rangeOfData(nl, options: [], range: NSMakeRange(pos, data.length - pos))
if range.location == NSNotFound {
return // File has less than `numLines` lines.
}
lineNo++
pos = range.location + range.length
}
// Now `pos` is the position where line number `numLines` begins.
let trimmedData = data.subdataWithRange(NSMakeRange(pos, data.length - pos))
trimmedData.writeToURL(fileURL, atomically: true)
} catch let error as NSError {
print(error.localizedDescription)
}
}
I have updated Martin R answer to Swift 3, and I also changed it so that we can pass the number of lines to keep instead of the number of lines to remove:
func removeLinesFromFile(fileURL: URL, linesToKeep numLines: Int) {
do {
let data = try Data(contentsOf: fileURL, options: .dataReadingMapped)
let nl = "\n".data(using: String.Encoding.utf8)!
var lineNo = 0
var pos = data.count-1
while lineNo <= numLines {
// Find next newline character:
guard let range = data.range(of: nl, options: [ .backwards ], in: 0..<pos) else {
return // File has less than `numLines` lines.
}
lineNo += 1
pos = range.lowerBound
}
let trimmedData = data.subdata(in: pos..<data.count)
try trimmedData.write(to: fileURL)
} catch let error as NSError {
print(error.localizedDescription)
}
}
Instead of writing the new line to the log, and processing the appended content afterwards, do both write and trimming in one step:
let fileString = (try? NSString(contentsOfURL: fileURL, encoding: NSUTF8StringEncoding)) as NSString? ?? ""
var lines = fileString.characters.split("\n").map{String($0)}
lines.append(message)
// this also more generic as it will remove any number of extra lines
lines.removeFirst(max(currentLineCount - maxLineCount), 0))
let newLogContents = lines.joinWithSeparator("\n")
(newLogContents as NSString).writeToURL(fileURL, atomically: true, encoding: NSUTF8StringEncoding)

Resources