-2

This is probably really simple, but I am new to working with JSON.

I am receiving this JSON string from an argument of an OSC message:

{"status":"ok","address":"\/workspaces","data":[{"version":"4.2.1","displayName":"Untitled Workspace","uniqueID":"82688E14-5E8F-428E-A781-8ABC920A5515","hasPasscode":false}]}

I am trying to convert it to a dictionary, so I can process the data. I have tried many answers on Stack Overflow, but this one seems the most relevant. However, it is throwing the error "The data couldn’t be read because it isn’t in the correct format."

Could someone advise why this string is the wrong format?

This is my code:

    func didReceive(_ message: OSCMessage){

    let jsonString = String(describing: message.arguments[0]!)

    func convertToDictionary(text: String) -> [String: Any]? {
        if let data = text.data(using: .utf8) {
            do {
                return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
            } catch {
                print(error.localizedDescription)
            }
        }
        return nil
    }

    let dict = convertToDictionary(text: jsonString)
    print(dict as Any)

}

Thanks so much!

Dan

EDIT: I have modified the code to remove the "\" which may have been contributing to the error. However it still throws the same error with the following code:

    func didReceive(_ message: OSCMessage){

    let jsonString = message.arguments[0] as! String
    let jsonStringEdit = jsonString.replacingOccurrences(of: "\\", with: "")

    func convertToDictionary(text: String) -> [String: Any]? {
        if let data = text.data(using: .utf8) {
            do {
                return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
            } catch {
                print(error.localizedDescription)
            }
        }
        return nil
    }

    let dict = convertToDictionary(text: jsonStringEdit)
    print(dict as Any)

}

EDIT 2:

SOLVED!

It seems there was some kind of null/non-printable character at the end of the string. I have solved the issue for now by removing it before parsing it. I have used this code to remove it:

jsonString.remove(at: jsonString.index(before: jsonString.endIndex))

Thanks to everyone for your suggestions!

  • 1
    `let jsonString = String(describing: message.arguments[0]!)`: It doesn't seem to be a good idea to use `describing:`. Why not directly `let jsonString = message.arguments[0] as! String`? (well it's still bad to force unwrap this way). – Larme Apr 23 '18 at 09:02
  • Thanks, the force unwrapping is just whilst I test. Your suggestion hasn't worked unfortunately. – Daniel Higgott Apr 23 '18 at 09:40

3 Answers3

0

The JSON response seems to be in correct form:

enter image description here

I guess the issue is in the string conversion, try using

let jsonString = message.arguments[0] as! String //Instead of string Describing
Abhirajsinh Thakore
  • 1,806
  • 2
  • 13
  • 23
0

This works:

let jsonStr = """
{
    "status":"ok",
    "address":"\\/workspaces",
    "data":[{
        "version":"4.2.1",
        "displayName":"Untitled Workspace",
        "uniqueID":"82688E14-5E8F-428E-A781-8ABC920A5515",
        "hasPasscode":false
     }]
}
"""

func convertToDictionary(text: String) -> [String: Any]? {
    if let data = text.data(using: .utf8) {
        do {
            return try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
        } catch {
            print(error.localizedDescription)
        }
    }
    return nil
}

let dict = convertToDictionary(text: jsonStr)
print(dict as Any)

I had to add an additional backslash escape char in the address value for it to work in a playground. This could be causing your issue.

Scriptable
  • 19,402
  • 5
  • 56
  • 72
  • Thanks, that works for me too, however it still doesn't work when I am passing in the string from the OSC message. I have modified the string to remove the "\" and it has the same error. The string is now: {"status":"ok","address":"/workspaces","data":[{"version":"4.2.1","displayName":"Secret Cinema Project Rain QLab A v53","uniqueID":"82688E14-5E8F-428E-A781-8ABC920A5515","hasPasscode":false}]} – Daniel Higgott Apr 23 '18 at 09:30
  • are you using the allowFragments option? – Scriptable Apr 23 '18 at 09:32
  • I am not sure tbh, it works in the playground, you need to make sure the JSON is definitely coming through exactly as it is displayed here. it can only be a data issue now – Scriptable Apr 23 '18 at 09:47
  • 1
    @Scriptable `.allowFragments` is only needed if the JSON is **not** a collection type. If the expected type is a dictionary the option is pointless. Unfortunately almost all online tutorials suggest that nonsense. – vadian Apr 23 '18 at 12:03
  • @vadian Surely because "who can do less, can do more". And I guess it saves someone one day, so from now on, let's put it everytime even if we don't understand why, and 99% of the case, it's useless indeed. – Larme Apr 23 '18 at 12:19
0

As it is the string should be parsable if you receive it from a service. However it will confuse the Swift parser if you try to use it as code without further escaping. However, since working with a [String:Any] is so incredibly messy (not to speak of unSwifty :-) I would suggest you do yourself a favour and try out the Codable protocol. That way you might end up with something like this in a Playground:

import Cocoa

let jsonData = """
{"status":"ok","address":"\\/workspaces","data":[{"version":"4.2.1", "displayName":"Untitled Workspace","uniqueID":"82688E14-5E8F-428E-A781-8ABC920A5515","hasPasscode":false}]}
""".data(using: .utf8)!

struct InnerData : Codable {
    let version: String
    let displayName: String
    let uniqueID: String
    let hasPasscode: Bool
}

struct AddressData : Codable {
    let status: String
    let address: String
    let data: [InnerData]
}

do {
    let adrData = try JSONDecoder().decode(AddressData.self, from:jsonData)
    print(adrData)
} catch {
    print(error)
}

which parses perfectly (given the escaped \) and will be much nicer to work with after that. Note that no escaping should be necessary if you get the string (including the \) from some service. If that still does not work please post the error message (and the exact line it occurs on).

Update after comments

I just added this to my Playground:

let jsonData2 = """
{"status":"ok","address":"/workspaces","data":[{"version":"4.2.1","displayName":"Untitled Workspace 1","uniqueID":"FE329024-DC98-4197-9B3F-2509851A1E50","hasPasscode":false}]}
""".data(using: .utf8)!

do {
    let adr = try JSONDecoder().decode(AddressData.self, from:jsonData2)
    print(adr.data[0].uniqueID)
} catch {
    print(error)
}

So the Stringyou provide parses perfectly, although the String you get seems to contain some Garbage at end. as the error message indicates. You might try to iterate over its final characters and print out their code points, this could easily point out some encoding issue if the code you are given is in ASCII, ISO-8859-1 or some other encoding in the first place. It is unfortunate to speculate on such things, but they may be hard to transfer into StackOverflow format since we are all Unicode here. Reinterpreting Strings in other encodings may lead to any number of weird behaviours, although for me it usually fails with an error when generating the String right away.

Accessing your InnerData struct is just plain and easy from the definition. Since your data tag contains an Array of InnerData you will have to subscript it before accessing its inner properties as pointed out above. Other than that it could not be more natural.

Patru
  • 4,481
  • 2
  • 32
  • 42
  • Still no joy. The error message is: dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Garbage at end." UserInfo={NSDebugDescription=Garbage at end.}))) This is the string I am passing to the JSONDecoder as data (as copied from the log): {"status":"ok","address":"/workspaces","data":[{"version":"4.2.1","displayName":"Untitled Workspace 1","uniqueID":"FE329024-DC98-4197-9B3F-2509851A1E50","hasPasscode":false}]} Could there be data that's not logged? – Daniel Higgott Apr 24 '18 at 12:29
  • Thanks for your help. I love your answer. One quick extra question. How do I access the InnerData struct? Say I wanted to print the displayName? – Daniel Higgott Apr 24 '18 at 15:04
  • @DanielHiggott it is a bad idea to put code (of more than one line) into comments. Try editing your question (though I am not sure you will be allowed to given your current points), that will allow you to greatly improve formatting. Try to print out your data in "as raw form as possible", since there seems to be some strange things "at the end". There are a whole bunch of non-printable characters in Unicode which might seem to "not log", so you might have to pry them apart one by one. – Patru Apr 25 '18 at 02:25