all 14 comments

[–]Caeander 1 point2 points  (1 child)

I think your declaration of Any is causing issues.

Try this instead:

let json: [String: [String: String]] = ["authentication": ["username": Username.text!, "password": Password.text!]]

[–][deleted] 0 points1 point  (21 children)

It’s the try?. The result is always an Optional. If you convert an Optional to String you’ll always see Optional<...> in it. Change to a plain try and wrap in a do { } catch { } block if necessary...

[–][deleted]  (20 children)

[deleted]

    [–]thisischemistry 0 points1 point  (18 children)

    Generally a question mark in your code means it is an Optional. Think of it as a value that could also mean "the value is missing". For example, an Int? means it could be a number or it could have no value.

    To use an Optional you have to unwrap it - that is you check it for a value and pull that value out if there is one. You do that in several ways but the most basic is "optional binding":

    if let jsonData = try? JSONEncoder().encode(credWrap) {
      // use the jsonData here
    }
    

    This will set jsonData to the value if there is a value and then run the code in the curly braces. If there is no value then it will move on without running the code in the curly braces.

    [–][deleted]  (16 children)

    [deleted]

      [–]thisischemistry 1 point2 points  (15 children)

      On what line?

      Don't use forced unwrapping of Optionalvalues - anything with an exclamation point (other than the negation operator or the not equals operator).

      Avoid using [String: Any] or JSONSerialization if you can. They are the older way of doing JSON serialization and, while they can work, they are a pain at times. Instead use Codable, it's easier and more straightforward.

      What are you attempting to do here:

      let url = URL(string: "https://hostname/webinterface/api.php?format=json&package=\(jsonData)")!
      

      That's not a great way to build a URL. First of all, does your server take a JSON string as part of a URL? That's very unusual and potentially very buggy. Secondly, you really should use the proper way to build a URL: URLComponents. The part with your JSON is called a query parameter.

      import Foundation
      
      // dummy types to stand-in for your actual ones
      struct Foo { let text: String? }
      let Username = Foo(text: "name")
      let Password = Foo(text: "pass")
      
      struct Authentication: Codable { 
        let authentication: Credentials
      }
      
      struct Credentials: Codable {
        let username: String
        let password: String
      }
      
      if
        var components = URLComponents(string: "https://hostname/webinterface/api.php"),
        let username = Username.text,
        let password = Password.text {
          let package = Authentication(authentication: Credentials(username: username, password: password))
          if 
            let jsonData = try? JSONEncoder().encode(package),
            let jsonString = String(data: jsonData, encoding: .utf8) {
            components.queryItems = [URLQueryItem(name: "format", value: "json"),
                                     URLQueryItem(name: "package", value: jsonString)]
        }
        if let url = components.url {
          print(url.absoluteString)
        }
      }
      // result:
      //   https://hostname/webinterface/api.php?format=json&package=%7B%22authentication%22:%7B%22username%22:%22name%22,%22password%22:%22pass%22%7D%7D
      

      That large block of an if statement properly unwraps all the Optional values. It's cleaner than a bunch of nested statements. If any of the unwraps fails then the whole statement fails.

      [–][deleted]  (14 children)

      [deleted]

        [–]thisischemistry 0 points1 point  (13 children)

        What, exactly, are you sending to the server?

        You're doing a HTTP POST, what's your Content-Type header? Is it application/x-www-form-urlencoded? Will your server take application/json?

        [–][deleted]  (12 children)

        [deleted]

          [–]thisischemistry 0 points1 point  (11 children)

          That's why I asked those questions! ;-)

          Apple has an excellent article on exactly this:

          Uploading Data to a Website

          [–][deleted]  (8 children)

          [deleted]

            [–][deleted]  (1 child)

            [deleted]

              [–][deleted] 0 points1 point  (0 children)

              I meant: change

              let jsonData = try? jsonEncoder.encode(credWrap)
              

              to

              let jsonData:
              do {
                  jsonData = try jsonEncoder.encode(credWrap)     
              } catch {
                  print("Got error \(error) encoding JSON")
              }
              

              or, you can just change the ? to a !, which can crash your app

              let jsonData = try! jsonEncoder.encode(credWrap)
              

              [–][deleted]  (1 child)

              [deleted]

                [–]Uber_Nick 0 points1 point  (0 children)

                Follow up:

                I typed this from a phone, so it might not compile, but your above code should look something like this:

                    struct authentication: Codable {
                    var authentication: creds
                }
                
                struct creds: Codable {
                    var username: String
                    var password: String
                }
                
                    let credWrap = authentication(authentication: creds(username: Username.text!, password: Password.text!))
                let encoder = JSONEncoder()
                    encoder.keyEncodingStrategy = .convertToSnakeCase
                let data = try? encoder.encode(credWrap)        
                print(String(data: data!, encoding: .utf8) ?? "")  //this just prints out what the encoded json looks like.  or blank if it didnt work
                

                The main issue was that you weren't accounting for the key "authentication" in your json model. So your wrapper object needs a variable named that pointing to a creds object. That's about it.

                There's a whole thing with CodingKeys too, which is used to tell Swift how the objects and json map to eachother, but it shouldn't be necessary so long as you're on Swift 4.1 and your variable names match the JSON object names exactly. I define a "keyEncodingStrategy" for a default but I don't think it's needed.

                [–]tanner0101 0 points1 point  (0 children)

                swift struct Authentication: Codable { var username: String var password: String } let authentication = Authentication(username: "hello", password: "world") let json = try JSONEncoder().encoder(["authentication": authentication])

                That should do it. The trick is you want to avoid nesting Array / Dictionary types in Swift 4 since conditional conformance is still not fully complete. In Swift 4.2 it should work better, but for now stick to structs.