JSON into array regardless of depth - json

First question here so please bear with me on that one.
I'm trying to do a class that will handle http requests.
The server will definitely reply in JSON so - on top of handling requests - I'd like it to turn the JSON data into dictionaries as well.
As of now, I have this but I find it rather inconvenient ...
guard let json = try? JSONSerialization.jsonObject(with:data, options .mutableContainers) as? [String:Any] else { return }
I want this class to be useful in as many situations as possible. There will be situations in which the server will send 4 or 5d json.
My question is the following :
Can't I convert the json into an array, regardless of the depth it will have, php style?

Related

Swift: Cast Any or AnyObject to JSON

I want to wrap data to JSON before sending.
func request(content: [String]) {
try? JSONEncoder().encode(content)
This worked well when sending arrays of string. However, I would also like to send other structures that can be represented in JSON.
For example arrays containing arrays, or dictionaries.
My idea was to make it
func request(content: Any) {
try? JSONEncoder().encode(content)
Which gives Protocol 'Any' as a type cannot conform to 'Encodable'
How can I make the parameter as generic as possible so what can be formed as JSON gets formed as JSON and whatever fails, fails? I do understand I can't JSONify everything but I would like to be able to do it with things that I know of can be represented in JSON form. I found things like this https://stackoverflow.com/a/64471720/2161301 but apparently you can't do that on "normal" functions
You can use AnyCodable in your project.
Then you will be allowed to use [String: AnyEncodable] / [AnyEncodable] etc. in the places where you are not able to use Any while trying to use JSONEncoder api.

How to make a swift Codable struct that will decode and encode a MongoDB _id ObjectId() in JSON

I'm a very new developer(this is my first dev job) building a Swift/iOS application for creating/processing orders, and the server is sending me a ObjectID() object in JSON with every product I look up. After some research, it seems like this is a MongoDB object.
The gentleman coding my API routes wants me to grab that object for every product the server sends me, so I can send it back to the server with any orders that include that product. He says that will make it much easier for him to access the product when processing new orders.
So far, I've had no trouble decoding the JSON the server is sending me, because it's been in formats like String, Int, Float, etc., or just another JSON object that needs a new Codable struct full of more of the same.
When it comes to creating a Codable struct for an ObjectID, I don't know what keys/value types (properties/value types, whatever terminology you want to use) to tell it to expect. Or if this is even the correct way to go about it.
This is what I have right now:
import Foundation
struct ProductData: Codable {
let _id : ObjectId
let productId : String
let description : String
let ...
}
The ObjectId type appearing above is a custom Codable struct that I haven't built yet, because I'm not sure how. I imagine it should look something like this:
import Foundation
struct ObjectId : Codable {
let someVariableName : SomeCodableType
let ...
}
I don't know what the variable name or the type would be. I understand that it has a timestamp and some other information inside of it, and I've read about it being represented as a string, but I get the feeling if I try something like let _id:String in my product Codable struct, it won't decode/encode the way I'm imagining.
I'm wondering how to build a "type" that will properly catch/decode the _id object that is being thrown at me. If there's a way to simply hold that data without decoding it, and just send it back when I need to later, that would also suit my purposes.
EDIT:
After some experimentation, I found this raw JSON for the _id object:
"_id":{"$id":"58071f9d3f791f4f4f8b45ff"}
Dollar signs are not allowed in Swift variable/property names, so I'm unsure how to proceed in a way that satisfies both the incoming AND outgoing - I could make a custom key by manually initializing the JSON so it will properly work with Swift, but I'm unsure if there's a way to reverse that when encoding the same object back into JSON to send back to the server.
Catching the JSON as a Dictionary of [String:String] seemed to do the trick.
import Foundation
struct ProductData: Codable {
let _id : [String:String]
let productId : String
let description : String
let ...
}
However, the server is struggling to convert this back into an OrderId() object - I'm guessing that the translation from "$id":OrderId("someid") to "$id":"someid" is not what should happen.

JSON string parsing in SWIFT

EDITED:
I ended up asking the vendor to change the implementation of sending the JSON
I want to parse a JSON string into a Dictionary using swift.
The plist key has the following value:
"{runid:\"8090\",status_id:\"5\"}"
and when I convert this into a String object, it looks like this "\"{runid:\\\"8488\\\",testids:[\"7480769\"]}\""
Code
let data = theString.data(using: .utf8)
let jsonObject = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments)
I have already gone through various posts and am not able to use the conventional solution we use in our daily lives.
Following things I already know:
The keys are not properly formatted in quotes
This is ideally not structured in the conventional way for parsing.
NOTE
Important thing to note is that I will not be able to change the format, because this value is coming from a third party vendor.
Another thing to note is that this string is being successfully parsed by the JAVA team in the company
I don't know if it covers all cases, but this is a solution using Regular Expression.
It searches for a pattern
{ or ,
one or more alphanumeric characters
:
and captures the first and second condition. Then it replaces the found match by adding the quotes:
let theString = "{runid:\"8090\",status_id:\"5\"}"
let validJSONString = theString.replacingOccurrences(of: "([{,])(\\w+):", with: "$1\"$2\":", options: .regularExpression)
print(validJSONString)
Blame the third party vendor for that mess. Things won't change if nobody complains.

Get the raw data of a JSON number using NSJSONSerializer

I'm trying to parse a JSON structure that looks like this:
{"amount": -9.45}
Using JSONSerializer, the amount gets parsed as a floating point number leading to errors in the numbers. This is the code I currently have:
import Foundation
let jsonData = "{\"amount\": -9.45}".data(using: .utf8)!
let jsonObject = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]
jsonObject["amount"] // -9.449999999999999
Is there a way I can get to the underlying string that is being parsed into a floating point number so I can parse it myself?
Notes:
This is financial data so I'd like to parse it as either an Int or Decimal.
I cannot control the source of the data.
I'm trying to avoid pre-processing the data/writing my own JSON parser.
EDIT: After reviewing the code of the Swift version of JSONSerialization provided by Apple as part of the open source Foundation project, I do not believe that it's possible to solve this problem using the stock class. I made a patched version of the class that parses all numbers as Decimal's instead of Int's or Double's and that solved my problem.

Swift Vapor framework difference in JSON function calls

My question is what difference in Vapor JSON function calls return JSON(["foo":"bar"]) vs return try JSON(node: ["foo":"bar"])?
Both variants work, what is the right way?
Mixing them like return JSON(node: ["foo":"bar"]) or return try JSON(["foo":"bar"]) will make build fail.
import Vapor
let drop = Droplet()
drop.get("json") { req in
return JSON(["foo": "bar"])
}
drop.run()
I think I can answer this one. At first glance, these look pretty similar, but they're very different. The singular reason that everything comes back to in these two initializers is ... GENERICS.
No External Arg
No external arg initializer refers to JSON(["foo": "bar"]) above. We use these for non-failable initializers for types that can be directly represented in JSON. For example, [String: JSON], String, [JSON], Number(Int, UInt, Double), etc..
You may say, "wait a minute, I'm passing [String: String] above. Well, here's the thing ... actually we're not. JSON is ExpressibleAsStringLiteral so ["foo": "bar"] above actually becomes ["foo": JSON("bar")] and allows us to use the no-argument initializer.
With External Arg node:
We use the external argument to help the compiler disambiguate since we were unable to use the same external parameters for failable and non-failable initializers.
If you look through the node: initializer, it is a set of generic overloads that allow us to make things easier. As above we mentioned that we can pass ["foo": "bar"] directly because it converts to [String: JSON]. Well, if we have a type that is concretely [String: String] and we try to use JSON(stringDict) it'll fail. If however we use try JSON(node: stringDict) we can use generics to know that String is NodeRepresentible and we have enough context to properly convert it to JSON. Or at least try to!
By having the node: initializer, we can allow multiple different generic variants and work with multiple different types.
:)
Hope this clears some things up, this is a pretty nuanced area of the code base, happy to elaborate more.