Skip to content

Instantly share code, notes, and snippets.

@robertmryan
Last active November 14, 2019 02:02
Show Gist options
  • Save robertmryan/03542e2da539ad7f51c2540bafdad97e to your computer and use it in GitHub Desktop.
Save robertmryan/03542e2da539ad7f51c2540bafdad97e to your computer and use it in GitHub Desktop.
struct Cost: Codable {
let id: String
let label: String
let value: String
}
let cost = Cost(id: "something", label: "something", value: "something")
let jsonData = try! JSONEncoder().encode(cost) // this works fine
let object = try! JSONSerialization.jsonObject(with: jsonData) // this works fine
@robertmryan
Copy link
Author

Bottom line, I can’t reproduce your problem. It works fine for me. I took your JSON string, and decoded it fine.

@MohsenMoghimi
Copy link

thanks for your help, I tried your code but still have the problem

@MohsenMoghimi
Copy link

really?!?!?!
Even I had test it on " http://www.json4swift.com " it says that the json is not valid

@robertmryan
Copy link
Author

Yes, take everything from that comment, paste it into Xcode playground or an app, and run it. It works fine.

Now I did have troubles when I pasted your JSON into https://jsonlint.com, validated it, and then cut and pasted the JSON that this site formatted for me into Xcode. Or you could have troubles if you converted to Data using any encoding other than .utf8. But that code works (which is why I included everything from all the struct types, the JSON, and the decoding, yielding a complete MCVE.

@robertmryan
Copy link
Author

Does it tell you where the error is? JSONSerialization will, IIRC.

@MohsenMoghimi
Copy link

MohsenMoghimi commented Nov 9, 2019

I think I found the problem, the problem is my string encoded json most be like this:

{"billing":{"phone":"۰۹۳۵۴۰۰۹۷۴۰","city":"جاجرود","country":"IR","address_1":"کوچه اول سمت راست\t","last_name":"مقیمی","company":"","postcode":"۱۲۳۴۵۶۷۸۹","email":"","address_2":"","state":"THR","first_name":"محسن"},"set_paid":false,"line_items":[{"product_id":158243,"quantity":1,"subtotal":"20000","variation_id":null},{"product_id":158236,"quantity":1,"subtotal":"17500","variation_id":null}],"payment_method_title":"بانک سامان","shipping_lines":[{"instance_id":4,"taxes":null,"method_title":"نرخ ثابت","id":4,"method_id":"flat_rate","title":"پست سفارشی","total":null,"settings":{"min_amount":null,"cost":{"id":"cost","label":"هزینه","value":"8900"}},"total_tax":null}],"coupon_lines":[],"customer_id":8360,"payment_method":"WC_Saman_Gateway","shipping":{"city":"جاجرود","country":"IR","address_1":"کوچه اول سمت راست\t","last_name":"مقیمی","company":null,"postcode":"۱۲۳۴۵۶۷۸۹","address_2":"","state":"THR","first_name":"محسن"}}

but its like this:

{\"billing\":{\"phone\":\"۰۹۳۵۴۰۰۹۷۴۰\",\"city\":\"جاجرود\",\"country\":\"IR\",\"address_1\":\"کوچه اول سمت راست\\t\",\"last_name\":\"مقیمی\",\"company\":\"\",\"postcode\":\"۱۲۳۴۵۶۷۸۹\",\"email\":\"\",\"address_2\":\"\",\"state\":\"THR\",\"first_name\":\"محسن\"},\"set_paid\":false,\"line_items\":[{\"product_id\":158243,\"quantity\":1,\"subtotal\":\"20000\",\"variation_id\":null},{\"product_id\":158236,\"quantity\":1,\"subtotal\":\"17500\",\"variation_id\":null}],\"payment_method_title\":\"بانک سامان\",\"shipping_lines\":[{\"instance_id\":4,\"taxes\":null,\"method_title\":\"نرخ ثابت\",\"id\":4,\"method_id\":\"flat_rate\",\"title\":\"پست سفارشی\",\"total\":null,\"settings\":{\"min_amount\":null,\"cost\":{\"id\":\"cost\",\"label\":\"هزینه\",\"value\":\"8900\"}},\"total_tax\":null}],\"coupon_lines\":[],\"customer_id\":8360,\"payment_method\":\"WC_Saman_Gateway\",\"shipping\":{\"city\":\"جاجرود\",\"country\":\"IR\",\"address_1\":\"کوچه اول سمت راست\\t\",\"last_name\":\"مقیمی\",\"company\":null,\"postcode\":\"۱۲۳۴۵۶۷۸۹\",\"address_2\":\"\",\"state\":\"THR\",\"first_name\":\"محسن\"}}

when I try to delete \ from text it transforms to this:

{billing:{phone:۰۹۳۵۴۰۰۹۷۴۰,city:جاجرود,country:IR,address_1:کوچه اول سمت راست\\t,last_name:مقیمی,company:,postcode:۱۲۳۴۵۶۷۸۹,email:,address_2:,state:THR,first_name:محسن},set_paid:false,line_items:[{product_id:158243,quantity:1,subtotal:20000,variation_id:null},{product_id:158236,quantity:1,subtotal:17500,variation_id:null}],payment_method_title:بانک سامان,shipping_lines:[{instance_id:4,taxes:null,method_title:نرخ ثابت,id:4,method_id:flat_rate,title:پست سفارشی,total:null,settings:{min_amount:null,cost:{id:cost,label:هزینه,value:8900}},total_tax:null}],coupon_lines:[],customer_id:8360,payment_method:WC_Saman_Gateway,shipping:{city:جاجرود,country:IR,address_1:کوچه اول سمت راست\\t,last_name:مقیمی,company:null,postcode:۱۲۳۴۵۶۷۸۹,address_2:,state:THR,first_name:محسن}}

@MohsenMoghimi
Copy link

MohsenMoghimi commented Nov 9, 2019

how can i get raid of \ without crupting object???
this is what I had done:

let jsonData = try! JSONEncoder().encode(order)
var strJson = String(data: jsonData, encoding: String.Encoding.utf8)

the strJson is :

"{\"billing\":{\"phone\":\"۰۹۳۵۴۰۰۹۷۴۰\",\"city\":\"جاجرود\",\"country\":\"IR\",\"address_1\":\"کوچه اول سمت راست\\t\",\"last_name\":\"مقیمی\",\"company\":\"\",\"postcode\":\"۱۲۳۴۵۶۷۸۹\",\"email\":\"\",\"address_2\":\"\",\"state\":\"THR\",\"first_name\":\"محسن\"},\"set_paid\":false,\"line_items\":[{\"product_id\":158236,\"quantity\":1,\"subtotal\":\"17500\",\"variation_id\":null},{\"product_id\":158243,\"quantity\":1,\"subtotal\":\"20000\",\"variation_id\":null}],\"payment_method_title\":\"بانک سامان\",\"shipping_lines\":[{\"instance_id\":4,\"taxes\":null,\"method_title\":\"نرخ ثابت\",\"id\":4,\"method_id\":\"flat_rate\",\"title\":\"پست سفارشی\",\"total\":null,\"settings\":{\"min_amount\":null,\"cost\":{\"id\":\"cost\",\"label\":\"هزینه\",\"value\":\"8900\"}},\"total_tax\":null}],\"coupon_lines\":[],\"customer_id\":8360,\"payment_method\":\"WC_Saman_Gateway\",\"shipping\":{\"city\":\"جاجرود\",\"country\":\"IR\",\"address_1\":\"کوچه اول سمت راست\\t\",\"last_name\":\"مقیمی\",\"company\":null,\"postcode\":\"۱۲۳۴۵۶۷۸۹\",\"address_2\":\"\",\"state\":\"THR\",\"first_name\":\"محسن\"}}"

@MohsenMoghimi
Copy link

when I paste my strJson this site automatically correct the \ in my json but its actually like this:

"{\"billing\":{\"phone\":\"۰۹۳۵۴۰۰۹۷۴۰\",\"city\":\"جاجرود\",\"country\":\"IR\",\"address_1\":\"کوچه اول سمت راست\\t\",\"last_name\":\"مقیمی\",\"company\":\"\",\"postcode\":\"۱۲۳۴۵۶۷۸۹\",\"email\":\"\",\"address_2\":\"\",\"state\":\"THR\",\"first_name\":\"محسن\"},\"set_paid\":false,\"line_items\":[{\"product_id\":158236,\"quantity\":1,\"subtotal\":\"17500\",\"variation_id\":null},{\"product_id\":158243,\"quantity\":1,\"subtotal\":\"20000\",\"variation_id\":null}],\"payment_method_title\":\"بانک سامان\",\"shipping_lines\":[{\"instance_id\":4,\"taxes\":null,\"method_title\":\"نرخ ثابت\",\"id\":4,\"method_id\":\"flat_rate\",\"title\":\"پست سفارشی\",\"total\":null,\"settings\":{\"min_amount\":null,\"cost\":{\"id\":\"cost\",\"label\":\"هزینه\",\"value\":\"8900\"}},\"total_tax\":null}],\"coupon_lines\":[],\"customer_id\":8360,\"payment_method\":\"WC_Saman_Gateway\",\"shipping\":{\"city\":\"جاجرود\",\"country\":\"IR\",\"address_1\":\"کوچه اول سمت راست\\t\",\"last_name\":\"مقیمی\",\"company\":null,\"postcode\":\"۱۲۳۴۵۶۷۸۹\",\"address_2\":\"\",\"state\":\"THR\",\"first_name\":\"محسن\"}}"

@robertmryan
Copy link
Author

The backslashes are perennial source of confusion. Let’s say you have a text string this is a "test" string. Depending upon how you display this (e.g. if you look at in the debugger, it may show it to you as "this is a \"test\" string". If you see the whole thing in quotes, then quotation marks inside that quoted string are shown with \ escape character, even though that \ isn’t really in the string.

And when you create your own string literals, you have to escape the " characters with \ (though, again, the \ character doesn’t actually appear in the string ... it’s only there for escaping):

let string = "This is a \"test\" string”

As Swift has evolved, they’ve come up with two ways to eliminate this need for escaping the quotation marks within a string. You can use the #" and "# pattern, e.g.

let string = #"this is a "test" string"#

Thus:

let string = #"{"billing":{"phone":"۰۹۳۵۴۰۰۹۷۴۰","city":"جاجرود","country":"IR","address_1":"کوچه اول سمت راست\t","last_name":"مقیمی","company":"","postcode":"۱۲۳۴۵۶۷۸۹","email":"","address_2":"","state":"THR","first_name":"محسن"},"set_paid":false,"line_items":[{"subtotal":"20000","product_id":158243,"quantity":1},{"subtotal":"17500","product_id":158236,"quantity":1}],"payment_method_title":"بانک سامان","shipping_lines":[{"instance_id":4,"id":4,"title":"پست سفارشی","method_title":"نرخ ثابت","settings":{"cost":{"id":"cost","label":"هزینه","value":"8900"}},"method_id":"flat_rate"}],"coupon_lines":[],"customer_id":8360,"payment_method":"WC_Saman_Gateway","shipping":{"city":"جاجرود","country":"IR","address_1":"کوچه اول سمت راست\t","postcode":"۱۲۳۴۵۶۷۸۹","last_name":"مقیمی","address_2":"","state":"THR","first_name":"محسن"}}"#

Or you can use the """ convention (which not only eliminates this need for escaping, but also allows you to write multi line strings):

let string = """
    this is a "test" string
    """

Or

let string = """
    {"billing":{"phone":"۰۹۳۵۴۰۰۹۷۴۰","city":"جاجرود","country":"IR","address_1":"کوچه اول سمت راست\t","last_name":"مقیمی","company":"","postcode":"۱۲۳۴۵۶۷۸۹","email":"","address_2":"","state":"THR","first_name":"محسن"},"set_paid":false,"line_items":[{"subtotal":"20000","product_id":158243,"quantity":1},{"subtotal":"17500","product_id":158236,"quantity":1}],"payment_method_title":"بانک سامان","shipping_lines":[{"instance_id":4,"id":4,"title":"پست سفارشی","method_title":"نرخ ثابت","settings":{"cost":{"id":"cost","label":"هزینه","value":"8900"}},"method_id":"flat_rate"}],"coupon_lines":[],"customer_id":8360,"payment_method":"WC_Saman_Gateway","shipping":{"city":"جاجرود","country":"IR","address_1":"کوچه اول سمت راست\t","postcode":"۱۲۳۴۵۶۷۸۹","last_name":"مقیمی","address_2":"","state":"THR","first_name":"محسن"}}"#
    """

For more information, see The Swift Programming Language: String Literals.

@MohsenMoghimi
Copy link

MohsenMoghimi commented Nov 9, 2019

let order = NewOrder(billing: billing, couponLine: [], customerId: customerId, lineItems: lineItems, paymentMethod: paymentMethod, paymentMethodTitle: paymentMethodTitle, setPaid: false, shipping: shipping, shippingLines: shippingLine)
let jsonData = try! JSONEncoder().encode(order)
print(JSONSerialization.isValidJSONObject(jsonData))

i was wrong, isValidJSONObject still prints false

@robertmryan
Copy link
Author

robertmryan commented Nov 9, 2019

It’s either:

let foo = "some \"quoted\" string"

or you can use extended string delimiters with #" and "#:

let foo = #"some "quoted" string"#

or you can use the multiline string literal, with """ (note, three, not two "):

let foo = """
    some "quoted" string
    """

If you want to do string interpolation, it’s either:

let bar = "the value is \(foo)"

Or within the extended string delimiters, you can use \#(foo) for interpolation:

let bar = #"the value is \#(foo)"#

Or you can use the multiline string literal with the standard string interpolation syntax:

let bar = """
    the value is \(foo)
    """

@robertmryan
Copy link
Author

robertmryan commented Nov 9, 2019

let order = NewOrder(billing: billing, couponLine: [], customerId: customerId, lineItems: lineItems, paymentMethod: paymentMethod, paymentMethodTitle: paymentMethodTitle, setPaid: false, shipping: shipping, shippingLines: shippingLine)
let jsonData = try! JSONEncoder().encode(order)
print(JSONSerialization.isValidJSONObject(jsonData))

i was wrong, isValidJSONObject still prints false

You misunderstand the purpose of isValidJSONObject. This method is just telling you whether you can take the object and pass it to JSONSerialization.data(withJSONObject:) without failing. Unlike JSONEncoder, JSONSerialization is extremely limited in terms of what it can encode; see “Overview” section of JSONSerialization documentation for specifics.

But you pass it an object to be encoded, not the Data that is the raw JSON:

let foo = ["a": 1]
print(JSONSerialization.isValidJSONObject(foo))  // true

let bar = ["a", "b", "c"]
print(JSONSerialization.isValidJSONObject(bar))  // true

let baz = 42
print(JSONSerialization.isValidJSONObject(baz))  // false because it has to be array or dictionary

let qux = [NSObject()]
print(JSONSerialization.isValidJSONObject(qux))  // false because the object in the array cannot be represented

@robertmryan
Copy link
Author

If you want to see if the Data is valid JSON, you’ll have to actually attempt the decode...

@MohsenMoghimi
Copy link

the problem solved, it was just a type mismatch, one of the numeric parameters most be string but I send it in Integer.
thanks a lot for your helps my friend, I so sorry for wasting your time.

@robertmryan
Copy link
Author

👍

@MohsenMoghimi
Copy link

hi again my friend, I'm have issue with my network layer, would you please help me???

@robertmryan
Copy link
Author

Go ahead an add your question here. Worst case scenario, if it’s too complicated, I might suggest that you post it as a proper question on StackOverflow, as I’m buried right now.

@MohsenMoghimi
Copy link

I really don't know about it complication, I will ask and you take decision that its complicated or not.

@MohsenMoghimi
Copy link

MohsenMoghimi commented Nov 13, 2019

I have APIClient in my project and this is the code:

protocol APIClientType {
    func request<M: Codable>(_ endpoint: URLRequestConvertible) -> Single<M>
}

final class NewAPIClient: NSObject, URLSessionDelegate, APIClientType {
    
    var session: URLSession!
    
    override init() {
        super.init()
        
        let urlsession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main)
        urlsession.configuration.allowsCellularAccess = true
        urlsession.configuration.timeoutIntervalForRequest = TimeInterval(10.0)
        
        self.session = urlsession
    }
    
    func request<M: Codable>(_ endpoint: URLRequestConvertible) -> Single<M> {
        return Single<M>.create { [unowned self] single in

            let request = endpoint.request()

            #if DEBUG
            print("REQUEST: \(debugPrint(request))")
            #endif
            
            let task = self.session.dataTask(with: request) { (data, response, error) in
                if let responseError = error {
                    single(.error(responseError))
                }
                
                #if DEBUG
                print("RESPONSE: \(debugPrint(response ?? "NULL"))")
                #endif
                
                if let httpResponse = response as? HTTPURLResponse {
                    switch httpResponse.statusCode {
                    case 200, 201:
                        guard let data = data else {
                            break
                        }
                        
                        do {
                            let model = try JSONDecoder().decode(M.self, from: data)
                            single(.success(model))
                        } catch let myJSONError {
                            single(.error(myJSONError))
                        }
                        
                    case 404:
                        single(.error(APIError.NotFound))
                        
                    default:
                        break
                    }
                }
            }
            
            task.resume()
            
            return Disposables.create {
                task.cancel()
            }
        }
    }
}

@MohsenMoghimi
Copy link

MohsenMoghimi commented Nov 13, 2019

in 2 case of web services the response is simple text not a json, something like "the task done", my issue is when I intent to get this responses the " request " function returns myJSONError, how can I modify this class to return a simple string response?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment