low speed working with swiftyJSON - json

I'm using SwiftyJSON pod and I got a JSON like this :
I'm using a simple for loop in this JSON. but the problem is that it has a very low speed, it takes about 4 seconds to do this for loop.
this my code :
for _ in jsonDariafti!["contacts"]{
if (jsonDariafti!["contacts"][i]["name"].stringValue.range(of: txtSearch.text!) != nil) {
arrayINdexPathSearch.append(i)
}
i = i + 1
}
I'm trying to do a search in this JSON. for each word that user typed he has to wait about 4 seconds. I use below for loop and change it to an array and then searched in array. it works fine. but still very low in the initial for loop, it takes about 5 seconds :
for _ in jsonDariafti!["contacts"]{
arraySearch.append(jsonDariafti!["contacts"][i]["name"].stringValue)
i = i + 1
}
is there any better way using JSONS? or any advice using JSONs?
edit :
this is how I get JSON:
#import SwiftyJSON
#import Alamofire
func getdata(){
Alamofire.request("http://rsptint.ir/getSefaresh.php", method: .get).responseJSON {
response in
if response.result.isSuccess {
self.jsonDariafti = JSON(response.result.value!)
} else{
self.showAlert(message: "مشکل در ارتباط با اینترنت")
}
}
}

I would strongly recommend ditching SwiftyJSON and the string-and-dictionary based searching you're doing, and replacing this with much simpler code based on Codable.
struct Sefaresh: Codable {
let contacts: [Contact]
}
struct Contact: Codable {
let code, name, id, carton, tedad, litr, vahed, vazn: String
}
let sefaresh = try? JSONDecoder().decode(Sefaresh.self, from: jsonData)
your first loop then becomes
for (index, contact) in sefaresh.contacts.enumerated() {
if contact.stringValue.range(of: txtSearch.text!) != nil {
arrayIndexPathSearch.append(index)
}
}
and the second is simply
for contact in sefaresh.contacts {
arraySearch.append(contact.name)
}

This might not be an answer, but rather an elaboration on the question, but with a lot of mangling and tweaking I got the following to work:
import Cocoa
let jsonData = """
{ "contacts": [
{"code":"AC60-12","name":"\u{0633}\u{0641}\u{06cc}\u{062f} \u{067e}\u{0644}\u{06cc} \u{0627}\u{0648}\u{0631}\u{0647} \u{062a}\u{0627}\u{0646}-\u{06a9}\u{0627}\u{0631}\u{062a}\u{0646} 12\u{062a}\u{0627}\u{06cc}\u{06cc}","id":"5","carton":"1","tedad":"12","litr":"12","vahed":"","vazn":"1"},
{"code":"AC60-6","name":"\u{0633}\u{0641}\u{06cc}\u{062f} \u{067e}\u{0644}\u{06cc} \u{0627}\u{0648}\u{0631}\u{0647} \u{062a}\u{0627}\u{0646}-\u{06a9}\u{0627}\u{0631}\u{062a}\u{0646} 6\u{062a}\u{0627}\u{06cc}\u{06cc}","id":"4","carton":"1","tedad":"6","litr":"6","vahed":"","vazn":"1"},
{"code":"ME1019","name":"Brilliant White \u{06cc}\u{06a9} \u{0644}\u{06cc}\u{062a}\u{0631}\u{06cc} ME 1019","id":"516","carton":"1","tedad":"6","litr":"6","vahed":"","vazn":"1"}]
}
""".data(using: .utf8)!
struct Sefaresh: Codable {
let contacts: [Contact]
}
struct Contact: Codable {
let code, name, id, carton, tedad, litr, vahed, vazn: String
}
do {
let sefaresh = try JSONDecoder().decode(Sefaresh.self, from: jsonData)
print(sefaresh)
} catch {
print(error)
}
This looks even more horrible, but it might also point out some of the problems you encountered. It might have to do with the typical encoding of a Playground (which is UTF-8), but even then it compiles properly and the result contains a lot of characters which seem to originate from the same script as you posted.
While getting there I had to cope with a lot of errors during String parsing like
Expected hexadecimal code in braces after unicode escape
and I was only able to correct that manually, any search/replace pattern would fail and even after the introduction of the curly braces the editor would sometimes only search like a drag. You seem to have hit upon something that can thoroughly confuse Xcode or even the Swift String parser in general (though much less likely, since the Playground runs very quickly once it compiles without error).
I am not sure who or what created your escaped JSON in the first place, but if it was a language which still assumes that your unicode characters should all fit within 16 bits that might explain something.
Would you care to elaborate what created your JSON in the first place? Given my experience with the Playground (it became completely unresponsive until I started introducing newlines) I can easily believe that you triggered some edge case of Swifts String parsing. Given so many escapes in a form that Swift might not understand I am pretty sure anything might happen.
If it is possible to introduce newlines after the end of your contacts that could help and of course the braces would make all the difference since they seem to be the way Swift expects your escapes to come. Maybe some other encoding (like UTF-16?) might help, but I am not versed well enough with the workings of non-latin scripts to really be able to tell.

Related

Swift Decodable - Is it possible to read part of a tree as a string rather than a data structure?

Here's my problem. Let's say I have a JSON structure that I'm reading using Swift's Codable API. What I want to do is not decode part of the JSON but read it as a string even though it's valid JSON.
In a playground I'm messing about with this code:
import Foundation
let json = #"""
{
"abc": 123,
"def": {
"xyz": "hello world!"
}
}
"""#
struct X: Decodable {
let abc: Int
let def: String
enum CodingKeys: String, CodingKey {
case abc
case def
}
init(decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
abc = try container.decode(Int.self, forKey: .abc)
var defContainer = try container.nestedUnkeyedContainer(forKey: .def)
def = try defContainer.decode(String.self)
// def = try container.decode(String.self, forKey: .def)
}
}
let x = try JSONDecoder().decode(X.self, from: json.data(using: .utf8)!)
Essentially I'm trying to read the def structure as a string instead of a dictionary.
Any clues?
If the resulting string doesn't need to be identical to the corresponding text in the JSON file (i.e. preserve whatever white space is there, etc.), just decode the entire JSON, and then encode the part that you want as a string, and construct a string from the resulting data.
If you do want to preserve exactly the text in the original JSON, including white space, then you'll do better to get that string some other way. Foundation's Scanner class makes it pretty easy to look for some starting token (e.g. "def:") and then read as much data as you want. So consider decoding the JSON in one step, and then separately using a Scanner to dig through the same input data to get the string you need.
Definitely not using JSONDecoder. By the time init(from:) is called, the underlying data has already been thrown away. However you do it, you'll need to parse the JSON yourself. This isn't as hard as it sounds. For example, to extract this string, you could use JSONScanner, which is a few hundred lines of code that you can adjust as you like. With that, you can do things like:
let scanner = JSONScanner()
let string = try scanner.extractData(from: Data(json.utf8), forPath: ["def"])
print(String(data: string, encoding: .utf8)!)
And that will print out:
{
"xyz": "hello world!"
}
(Note that RNAJSON is a sandbox framework of mine. It's not a production-ready framework, but it does a lot of interesting JSON things.)
Integrating this into a system that decodes this in a "Decoder-like" way is definitely buildable along these lines, but there's no simple answer down that path. Caleb's suggestion of re-encoding the data into a JSON string is definitely the easiest way.
Using RNAJSON again, there's a type called JSONValue that you can use like this:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
abc = try container.decode(Int.self, forKey: .abc)
let defJSON = try container.decode(JSONValue.self, forKey: .def)
def = String(data: try JSONEncoder().encode(defJSON), encoding: .utf8)!
}
This will make def be a JSON string, but it doesn't promise that key order is maintained or that whitespace is preserved.
Thanks everyone. It's an interesting problem. Not so much in the rather simple example I gave, but in the actual problem I'm thinking about I want to include mustache templating inside the text I'm decoding (which by the way could be JSON or YAML). The top level parts of the data are fixed in that they have pre-defined keys so reading them is fine. but there is a point in the data where the developer generating it can pretty much include any data structure they like, and that data structure can include mustache keys. ie. abc: {{some-value}} which of course, makes no sense to a decoder.
There's lots of ways this could go wrong but I'm thinking that really I would need to run mustache across the entire data structure before decoding it, where as currently I'm decoding then running mustache and to do that I'm already setting the unknown part as a string and reading it as such. I'm contemplating the idea that it would be much nicer if it was just JSON/YAML rather than a string containing JSON/YAML. But it might not be possible :-)

Parsing identical foreign dictionary JSON file entries, but having different meanings

I am reading in the following bilingual English <-> Japanese dictionary data from the iOS app bundle directory formatted as a json file having identical entries, but with different meanings (i.e. abandon) as shown in 'json data set 1' below:
{
"aardvark":"土豚 (つちぶた)",
"abacus":"算盤 (そろばん)",
"abacus":"十露盤 (そろばん)",
"abalone":"鮑 (あわび)",
"abandon":"乗り捨てる (のりすてる)(a ship or vehicle)",
"abandon":"取り下げる (とりさげる)(e.g. a lawsuit)",
"abandon":"捨て去る (すてさる)(ship)",
"abandon":"泣し沈む (なきしずむ)oneself to grief",
"abandon":"遺棄する (いき)",
"abandon":"握りつぶす (にぎりつぶす)",
"abandon":"握り潰す (にぎりつぶす)",
"abandon":"見限る (みかぎる)",
"abandon":"見切り (みきり)",
"abandon":"見捨てる (みすてる)",
"abandon":"突き放す[見捨てる (つきはなす)",
"abandon":"放り出す (ほうりだす)",
"abandon":"廃棄 (はいき)",
"abandon":"廃棄する (はいき)",
"abandon":"放棄する (ほうき)",
}
I am using the code snippet below to read in the data from the app.bundle directory:
var vocab:[String:String] = [:]
do {
let path = Bundle.main.path(forResource: "words_alpha", ofType: "json")!
let text = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
do {
vocab = try JSONDecoder().decode([String: String].self, from: Data(text.utf8))
print(text)
}
} catch {
print(error)
}
}
Question: Only the first entry of a duplicate entry is being read in whereas I would like to have all duplicate entries read in as multiple definitions for a single dictionary item/term.
One solution is to reformat duplicate entries in the json data as shown below by adding line returns between different definitions in 'json data set 2':
"abandon":"乗り捨てる (のりすてる)(a ship or vehicle)\n\n取り下げる (とりさげる)(e.g. a lawsuit)\n\n捨て去る (すてさる)(ship)\n\n 泣し沈む (なきしずむ)oneself to grief\n\n",
However, that is a huge amount of work editing a 30MB json data file to make the above changes for duplicate items so I am looking for a quick and dirty way to use swift json string manipulations to read in the data 'as is' using the native 'data set 1' format with each entry being on a line by itself as shown below:
{
"abandon":"乗り捨てる (のりすてる)(a ship or vehicle)",
"abandon":"取り下げる (とりさげる)(e.g. a lawsuit)",
}
Have tried a number of approaches, but none have worked so far. Any suggestions very much appreciated.
Dictionary can't have the same key, there is unicity.
If you use for instance JSONLint, you'll have a "Error: Duplicate key 'abacus'" (it stops at first error found).
However, that is a huge amount of work editing a 30MB json data file to make the above changes for duplicate items so I am looking for a quick and dirty way
Instead of thinking that way, let's pre-process the JSON, and fix it!
So you could write a little script beforehand to fix your JSON. You can do it in Swift!
In a quick & dirty way, you could do this:
All definitions must be one line (else you might have to fix manually for them)
Create a fixJSON.swift file (in Terminal.app: $>touch fixJSON.swift), make it executable ($ chmod +x fixJSON.swift), put that code inside it:
#!/usr/bin/swift
import Foundation
func fixingJSON(_ input: String) -> String? {
let lines = input.components(separatedBy: .newlines)
let regex = try! NSRegularExpression(pattern: "\"(\\w+)\":\"(.*?)\",", options: [])
let output = lines.reduce(into: [String: [String]]()) { partialResult, aLine in
var cleaned = aLine.trimmingCharacters(in: .whitespaces)
guard !cleaned.isEmpty else { return }
if !cleaned.hasSuffix(",") { //Articially add a ",", for the last line case
cleaned.append(",")
}
guard let match = regex.firstMatch(in: cleaned, options: [], range: NSRange(location: 0, length: cleaned.utf16.count)) else { return }
guard let wordRange = Range(match.range(at: 1), in: cleaned),
let definitionRange = Range(match.range(at: 2), in: cleaned) else { return }
partialResult[String(cleaned[wordRange]), default: [String]()] += [String(cleaned[definitionRange])]
}
// print(output)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let asJSONData = try encoder.encode(output)
let asJSONString = String(data: asJSONData, encoding: .utf8)
// print(asJSONString!)
return asJSONString
} catch {
print("Error while encoding: \(error)")
return nil
}
}
func main() {
do {
//To change
let path = "translate.json"
let content = try String(contentsOfFile: path)
guard let output = fixingJSON(content) else { return }
//To change
let outputPath = "translate2.json"
try output.write(to: URL(fileURLWithPath: outputPath), atomically: true, encoding: .utf8)
} catch {
print("Oops, error while trying to read or write content of file:\n\(error)")
}
}
main()
Modify path/output path values, it's easier if you put it as the same place as the script file, then the path will be just the name of the file.
Then, in Terminal.app, just write $> ./fixJSON.swift
Okay, now, let's talk about what the script does.
As said, it's quick & dirty, and might have issues.
We read the content of the JSON with issue, I iterate over the lines, then used a regex, to find this:
"englishWord":"anything",
I artificially add a comma if there isn't (special case for the last entry of the JSON which shouldn't have one).
As to why, it's because there could be double quotes in a translation, so it could generate issues. It's just a quick & dirty fix. I might do better, but since it's a quick fix, spending more time to write beautiful code might be overkill for a one time use.
In the end, you'll have a [String: [String]] JSON.
This is fine JSON (except for the last comma, which isn't legal), but Swift's JSONDecoder can't handle it. JSON allows duplicate keys, but Swift doesn't. So you'll need to parse it by hand.
If your data is exactly as given, one record per line, with nothing "weird" (like embedded \" in the Strings), the easiest way to do that is to just parse it line by line, using simple String manipulation or NSRegularExpression.
If this is more arbitrary JSON, then you may want to use a full JSON parser that can handle this, such as RNJSON. Note that this is just a hobby project to build a JSON parser that exactly follows the spec, and as much intended as an example of how to write these things as a serious framework, but it can handle this JSON (as long as you get rid of that last , which is not legal).
import RNJSON
let keyValues = try JSONParser()
.parse(data: json)
.keyValues()
.lazy
.map({($0, [try $1.stringValue()])})
let translations = Dictionary(keyValues, uniquingKeysWith: +)
// [
"abandon": ["乗り捨てる (のりすてる)(a ship or vehicle)", "取り下げる (とりさげる)(e.g. a lawsuit)", "捨て去る (すてさる)(ship)", "泣し沈む (なきしずむ)oneself to grief", "遺棄する (いき)", "握りつぶす (にぎりつぶす)", "握り潰す (にぎりつぶす)", "見限る (みかぎる)", "見切り (みきり)", "見捨てる (みすてる)", "突き放す[見捨てる (つきはなす)", "放り出す (ほうりだす)", "廃棄 (はいき)", "廃棄する (はいき)", "放棄する (ほうき)"],
"aardvark": ["土豚 (つちぶた)"],
"abacus": ["算盤 (そろばん)", "十露盤 (そろばん)"],
"abalone": ["鮑 (あわび)"]
]
It's not that complicated a framework, so you could also adapt it to your own needs (making it accept that last non-standard comma, for example).
But, if possible, I'd personally just parse it line by line with simple String manipulation. That would be the easiest to implement using AsyncLineSequence, which would avoid pulling all 30MB into the memory before parsing.

Swift - Parsing JSON with Combine, URLSession DataTaskPublisher - Strange formatting

I'm looking for some guidance here on an issue I'm encountering. I am polling an API for stock data and instead of getting back an array or formatted JSON I get chunks of it. Each block of JSON seems to be properly formatted, but the JSON decoders are looking for a properly formatted array.
Prior to using combine I had a different working. I had to parse the data to remove 4 trailing characters, then I was left with lines of JSON that I could split by newline and then do a forEach loop on to parse.
This was messy but it works. The issue now is I'm polling streaming data for live quote updating in my UI. I need to have the JSON chunks read into my class, stored as a variable that will update the UI. I am not worried about all of that code once I can figure out how to get the data read properly.
I know my app is receiving the data because I see the stats in the debugger for network. I also test the same API call in a browser and see what data my app is receiving. I just don't know how to break it up in the combine framework.
I have been working with something like this so far:
return URLSession.shared.dataTaskPublisher(for: request)
.map { $0.data }
.handleEvents(receiveSubscription: { print("Receive subscription: \($0)") },
receiveOutput: { print("Receive output: \($0)") },
receiveCompletion: { print("Receive completion: \($0)") },
receiveCancel: { print("Receive cancel") },
receiveRequest: { print("Receive request: \($0)") })
//.decode(type: CryptoDataContainer.self, decoder: JSONDecoder())
.decode(type: Quote.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
The Quote struct is very basic:
struct Quote: Codable {
public var Symbol: String
public var Last: Double
public var Bid: Double
public var Ask: Double
public var Close: Double
}
JSON data is posted below, and it streams. I get a few chunks of it at a time, but if I can split by newline or by everything {.*} to get each JSON chunk and decode individually it would work great.
I'm really looking for Last price and Symbol out of the JSON. I could even just look for Last and start one individual streaming session for each symbol I need.
I will also need to split these up using a subject to multicast each publisher to multiple subscribers but I wanted to get the basic functionality working before complicating matters.
Update: From another post I found that the problem is the JSON formatting - The API is returning invalid JSON that is not formatted into an array - it is missing the surrounding []. This is why it doesn't parse. Another option may be to simply convert to string, add square brackets around all of the JSON passed down from the server (there is nothing but objects).. and then parse it correctly? I'm not sure how to do this within a combine map but that seems to be the path forward.
Here’s an example of the map I’m trying to use currently, since the data comes in as json chunks I need to parse them each individually.
.map ({
let stringRepresentation = String(data: $0, encoding: .utf8)
let outputJson = stringRepresentation!.split(whereSeparator: \.isNewline).map(String.init)
if outputJson.count > 1 {
print("DEBUG: outputJson.count > 1 code running..")
let formattedOutputJson:String = outputJson.joined(separator: ",")
let finalJson:String = "{\"quotes\":[" + formattedOutputJson + "]}"
let data:Data? = finalJson.data(using: .utf8)
return data!
} else {
print("DEBUG: outputJson.count = 1 (Else) code running..")
let finalJson:String = "{\"quotes\":[" + outputJson[0] + "]}"
let data:Data? = finalJson.data(using: .utf8)
return data!
}
})
Here is a better example of the JSON formatting in chunks I’m talking about:
{"heartbeat":4,"timestamp":"\/Date(1622898090819)\/"}
{"heartbeat":5,"timestamp":"\/Date(1622898095819)\/"}
{"heartbeat":6,"timestamp":"\/Date(1622898100819)\/"}
{"heartbeat":7,"timestamp":"\/Date(1622898105819)\/"}
{"heartbeat":8,"timestamp":"\/Date(1622898110819)\/"}
If I can even capture the heartbeat number as an Int to use I would be well on my way to getting my code working. The issue is I get zero data out of this. I know I’m connected from packet captures and various other tests. The data is getting to my system, and to the Xcode simulated app, I just don’t think I’m getting it to a point where I can print to the console correctly to display it. I have been working on that prior to attempting to update the UI directly.
{"Description":"E-Mini NASDAQ-100 Jun 2021","PreviousClose":13529.25,"PreviousVolume":552987,"AssetType":"FUTURE","CountryCode":"United States","Halted":false,"Bid":13513.75,"BidSize":3,"Ask":13514.25,"AskSize":1,"Currency":"USD","LastSize":1,"LastVenue":"CME","SymbolRoot":"NQ","Exchange":"CME","LastTradingDate":"2021-06-18","DisplayType":3,"FractionalDisplay":false,"MinPrice":1258200,"PointValue":20,"TradeTime":"2021-06-04T02:40:20.0000000+00:00","MaxPrice":1447600,"MinMove":25,"High52Week":14064,"Open":13531.75,"Low52Week":9591.75,"High":13541.75,"Low":13467.75,"Close":13513.75,"Symbol":"NQM21","Volume":23877,"DailyOpenInterest":231736,"ExpirationDate":"2021-06-18","Last":13513.75,"NameExt":"(D)","DataFeed":"TradeStation","IsDelayed":true,"Restrictions":[null],"VWAP":13499.877658013,"NetChange":-15.50,"NetChangePct":-0.1145665872091948925476282900,"High52WeekTimeStamp":"2021-04-29T00:00:00.0000000+00:00","Low52WeekTimeStamp":"2020-06-11T00:00:00.0000000+00:00","PreviousClosePriceDisplay":"13529.25","BidPriceDisplay":"13513.75","AskPriceDisplay":"13514.25","MinPriceDisplay":"1258200.00","MaxPriceDisplay":"1447600.00","High52WeekPriceDisplay":"14064.00","OpenPriceDisplay":"13531.75","Low52WeekPriceDisplay":"9591.75","HighPriceDisplay":"13541.75","LowPriceDisplay":"13467.75","ClosePriceDisplay":"13513.75","LastPriceDisplay":"13513.75","VWAPDisplay":"13499.88","FirstNoticeDate":"","StrikePrice":0,"TickSizeTier":0,"Underlying":"","StrikePriceDisplay":"0.00"}
{"Last":4188.5,"NameExt":"(D)","DataFeed":"TradeStation","IsDelayed":true,"Restrictions":[null],"VWAP":4184.92506004117,"Description":"E-mini S&P 500 Jun 2021","PreviousClose":4191.25,"PreviousVolume":1384271,"AssetType":"FUTURE","CountryCode":"United States","Halted":false,"Bid":4188.25,"BidSize":39,"Ask":4188.5,"AskSize":23,"Currency":"USD","LastSize":1,"LastVenue":"CME","SymbolRoot":"ES","Exchange":"CME","LastTradingDate":"2021-06-18","DisplayType":3,"FractionalDisplay":false,"MinPrice":3898,"PointValue":50,"TradeTime":"2021-06-04T02:40:10.0000000+00:00","MaxPrice":4484,"MinMove":25,"High52Week":4238.25,"Open":4190.75,"Low52Week":2977.8999,"Hi{"Volume":23880,"TradeTime":"2021-06-04T02:40:23.0000000+00:00","VWAP":13499.879455674,"Symbol":"NQM21"}
{"AskSize":18,"Symbol":"ESM21"}
{"Bid":13514.25,"BidSize":1,"AskSize":1,"BidPriceDisplay":"13514.25","Symbol":"NQM21","VWAP":13499.8800582884,"Volume":23881,"TradeTime":"2021-06-04T02:40:25.0000000+00:00"}
{"VWAP":4184.92521199492,"AskSize":19,"Volume":47807,"TradeTime":"2021-06-04T02:40:27.0000000+00:00","Symbol":"ESM21"}
{"Bid":13514,"BidSize":3,"BidPriceDisplay":"13514.00","Symbol":"NQM21","TradeTime":"2021-06-04T02:40:27.0000000+00:00","Ask":13514.75,"VWAP":13499.8806713352,"Close":13514.5,"Last":13514.5,"Volume":23882,"NetChange":-14.75,"NetChangePct":-0.1090230426668144945211301400,"AskPriceDisplay":"13514.75","ClosePriceDisplay":"13514.50","LastPriceDisplay":"13514.50"}
{"AskSize":18,"Symbol":"ESM21"}

Receiving Websocket data in Swift

I'm carrying this on from this question, since the focus has changed.
I am trying to send string data from a vapor server over a websocket. The client side is where the main question is. This code successfully receives the string, which is expected to be JSON (but not absolutely guaranteed -- out of scope).
switch message {
case .data(let data):
print("data: \(data)")
case .string(let str):
// let data = str.message(using: .utf8)
let jsonData = Data(str.utf8)
print("string: \(jsonData)")
do {
struct Person : Codable {
var name: String
}
let decoder = JSONDecoder()
let people = try decoder.decode([Person].self, from: jsonData)
print("result: \(people)")
} catch {
print(error.localizedDescription)
}
}
After some very helpful guidance, sending a string such as "{\"name\": \"Bobberoo\"}" will print out
string: 20 bytes
The data couldn’t be read because it isn’t in the correct format.
If I wrap it in braces "[{\"name\": \"Bobberoo\"}]" produces the more helpful but still mystifing (to me) output:
result: [wb2_socket_client.WebSocketController.(unknown context at $101a35028).(unknown context at $101a350c0).(unknown context at $101a35158).Person(name: "Bobberoo")]
Clearly, the decoding is happening, but it's wrapped in these contexts. What are they? I can see that the first is the instance of the WebSocketController. How do I access this data.
And as a non-inflammatory aside: managing JSON is a trivial operation in any number of contexts. Python/Flask, Node, Ruby/Rails and on and on; I've used all these and implementing this kind of interaction is trivial. In Swift, it's a horrible, underdocumented nightmare. At least, that's my experience. Why? I know the language is type safe, but this is ridiculous.
error.localizedDescription won't give you an error message that is useful message for debugging. On the other hand, if you print error directly:
print(error)
You'd get something along the lines of "expected to decode array but found dictionary instead", which is exactly what is happening in the case of
{
"name": "Bobberoo"
}
You are decoding a [Person].self, i.e. an array of Person, but your JSON root is not a JSON array. The above JSON can be decoded if you did:
let people = try decoder.decode(Person.self, from: jsonData)
Clearly, the decoding is happening, but it's wrapped in these contexts. What are they?
This is the default string representation of a type. Your Person struct does not conform to CustomStringConvertible or CustomDebugStringConvertible or TextOutputStreamable, so "an unspecified result is supplied automatically by the Swift standard library" (the link points to String.init(reflecting:), which presumably gets called somewhere along the way when you print the array of Person) and used as the string representation.
From what I can see, its current implementation is the fully qualified name of the struct - starting with the module, then the top-level class, then each enclosing scope, ending with the struct name, followed by the struct's members in brackets. It turns out that the enclosing scopes has no "names", and so are just called (unknown context at xxxxx). This is all very much implementation details, and things that you shouldn't care about.
What you should do, is provide an implementation of CustomStringConvertible:
struct Person: CustomStringConvertible {
...
var description: String { "name: \(name)" }
}
Now printing people gives:
[name: Bobberoo]
I can see that the first is the instance of the WebSocketController.
No. The WebSocketController is part of the fully qualified name of your Person struct. There is exactly one instance in your decoded array, and it's an instance of Person, as you would expect!
How do I access this data?
To access its name:
if let firstPerson = people.first {
let firstPersonsName = firstPerson.name
}

looking for most efficient way to display JSON data received via websocket, into a Xcode/Swift dynamic table

Currently I am working on a MacOS financial market trading application that could receive 10-20 websocket messages per second containing JSON data. For now, let's assume that once the data has been displayed in a table and a new message has been received, the previous data is no longer needed. What would be the recommended, and most efficient way to get the data into the table, and to keep updating it as fast as possible?
I have the websocket connection setup and working properly (using SocketRocket. Data is coming in as it should. But I really don't want to continue further without having a better understanding of the most efficient way to present that data into the table. I was reading about DictionaryArrayController, and maybe utilizing a database library, but I think the latter would would only add unnecessary overhead for what I am trying to do.
Below is a sample of the data that I am receiving.
{
"id":"5267",
"pt":"T",
"ls":["1"],
"lp":["11968"],
"t":["1571824228"],
"v":"133758",
"h":"11981",
"l":"11928",
"bp":["0","1","2","3","4","5","6","7","8","9"],
"bs":["14","73","87","66","74","96","98","85","111","104"],
"ap":["1","2","3","4","5","6","7","8","9","10"],
"as":["69","67","62","124","89","105","97","107","113","124"]
}
The only values that I will need to display & update to the table will be coming from keys: "ls" "lp" "v" "h" "l" "bs" "as"
I don't really need a detailed coded response, but I'd never refuse it, either. Mainly just looking for thoughts so I don't have to switch to a different way, later.
I'll also attach an screen capture of an Excel file showing about how the cells will be laid out. screenshot of desired layout
Have you tried using a JSON decoder, built in to Swift?
Create an appropriate using model, using the Decodable protocol. The property names and data types must match the incoming JSON exactly
struct IncomingData: Decodable {
{
let ls: [String],
let lp: [String],
let h: String,
let as: [String]
}
The set up the parse like this:
func parseJSON(_ incomingData: Data) -> IncomingData? {
let decoder = JSONDecoder()
do {
let parsedData = try decoder.decode(IncomingData.self, from: incomingData)
print("as: \(as)"
return parsedData
}
catch {
self.delegate?.didFailWithError(error: error)
return nil
}
}