2

I am trying to convert my swift struct to json format. There seems to be quite a few questions like this, but none of the solutions has worked for me so far.

Here is my struct

struct Rec : Codable {
    var name:String
    var time:Int
    var values:[String]

    var jsonRepresentation : String {
        return """
            {
                "name":"\(name)",
                "time":\(time),
                "values":\(values)
            }
        """
    }
}

Here is the json format that the external api recieves.

{
    "op" : "delete",
     "rec": { 
        "name" : "some string",
        "time":200,

        "values": [ 
            "127.0.0.1"
        ]
    }
}

Here is my function the creates the HTTP request

 private func createPatchRequest(op: String, url: URL, rec: Rec) throws -> URLRequest {
    let dictionary: [String: String] = [
        "op":op,
        "rec":rec.jsonRepresentation
    ]

    let jsonData = try JSONEncoder().encode(dictionary)

    var request = URLRequest(url: url)

    request.httpMethod = "PATCH"
    request.httpBody = jsonData
    request.addValue("application/json", forHTTPHeaderField:"Content-Type")

    return request
}

I always get a 400 and i believe that swift isn't' encoding my json properly. I tried the request on postman and it worked fine!

Here is how it looked on postman

{
    "op" : "delete",
    "rec": { 
        "name" : "some string",
        "time":600,
        "values": [ 
            "127.0.0.1"
        ]
    }
}

Any ideas? Thanks!

2
  • oops. just edited it. Its "rec" all the way Commented Mar 5, 2018 at 20:53
  • 7
    Since your struct is Codable, you don't have to do the weird manually created jsonRepresentation thing and then encode it, you can just directly call JSONEncoder().encode(rec). You would need to create a wrapper request struct that had op and rec properties and encode that one though Commented Mar 5, 2018 at 20:57

2 Answers 2

7

The problem is that the API expects rec to be a JSON object, but you're sending Rec as a string representation of a JSON Object. i.e. This is the JSON that you're sending:

{
  "rec": "{\n\"name\":\"some string\",\n\"time\":1,\n\"values\":[\"127.0.0.1\"]\n}",
  "op": "delete"
}

The ideal approach would be to create a Codable PatchRequest with a nested Rec struct and encode that. You can try the following snippet on Playground

struct Rec : Codable {
    var name:String
    var time:Int
    var values:[String]
}
struct PatchRequest : Codable{
    var op: String
    var rec: Rec
}

let rec = Rec(name: "some string",time: 600,values: ["127.0.0.1"])
let request = PatchRequest(op: "delete",rec: rec)

let jsonData = try JSONEncoder().encode(request)
let jsonString = String(data: jsonData, encoding: .utf8) 

This has the added benefit of reducing the number of parameters required by your API method and making the code cleaner overall:

private func createPatchRequest(url: URL, patchRequest: PatchReuest) throws -> URLRequest {
    let jsonData = try JSONEncoder().encode(patchRequest)
    
    var request = URLRequest(url: url)
    
    request.httpMethod = "PATCH"
    request.httpBody = jsonData
    request.addValue("application/json", forHTTPHeaderField:"Content-Type")
    
    return request
}

Lastly, I would recommend using let in your structs instead of var unless it's absolutely necessary.

Sign up to request clarification or add additional context in comments.

3 Comments

This worked perfectly. Is there a reliable way to convert a string directly into a json? I think my jsonRepresentation function is decent. Even printed it out and it looked fine. But it doesn't work.
Ideally, if an API requests a JSONObject, you ought to create a struct that corresponds to that object and encode it directly using JSONEncoder. Trying to construct a json by formatting a string can lead to weird errors, such as in your case, where you were sending a string representation of a JSON Object instead of an actual json object.
@SomeGuyFortune Your jsonRepresentation would actually work (the fact that it was working was by change, never encode JSON by yourself!) but once you put it into a dictionary, it couldn't work because you were trying to encode a JSON with a JSON string inside.
1

Your jsonRepresentation is a string but should be a dictionary. Update your struct this way:

struct Rec : Codable {
var name:String
var time:Int
var values:[String]

var jsonRepresentation: [String: Any] {
    return [
        "name": name,
        "time": time,
        "values": values
    ]
  }
}

Hope it helps! Please let me know if it worked.

2 Comments

Also, he should remove that \(...) everywhere and just use name, time and values directly! Values are still being converted to a String while you want an array of strings.
This is much cleaner than mine. But it seems like a better way would be to create a wrapper struct, that should also be Codable. So that swift can do a much better job encoding it to json. ` let jsonData = try JSONEncoder().encode(someEncodableStructObject)`

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.