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

robertmryan commented Nov 9, 2019

You didn’t provide all of your structs, so I recreated them using your pattern and looking at your JSON:

struct Billing: Codable {
    let first_name: String
    let last_name: String
    let company: String
    let address_1: String
    let address_2: String
    let city: String
    let state: String
    let postcode: String
    let country: String
    let phone: String
    let email: String
}

struct CouponLines: Codable {
    let subtotal: String
    let product_id: Int
    let quantity: Int
}

struct LineItem: Codable {
    let subtotal: String
    let product_id: Int
    let quantity: Int
}

struct Setting: Codable {
    let cost: Cost
}

struct Cost: Codable {
    let id: String
    let label: String
    let value: String
}

struct Shipping: Codable {
    let first_name: String
    let last_name: String
    let address_1: String
    let address_2: String
    let state: String
    let postcode: String
    let country: String
}

struct Shipping_lines: Codable {
    let instance_id: Int
    let id: Int
    let title: String
    let method_title: String
    let method_id: String
    let settings: Setting
}

struct NewOrder : Codable {
    let billing : Billing
    let coupon_lines : [CouponLines]?
    let customer_id : Int
    let line_items : [LineItem]
    let payment_method : String
    let payment_method_title : String
    let set_paid : Bool
    let shipping : Shipping
    let shipping_lines : [Shipping_lines]
}

and I decoded with both JSONDecoder and JSONSerialization without incident:

do {
    let json = try JSONSerialization.jsonObject(with: data)
    print("json", json)

    let object = try JSONDecoder().decode(NewOrder.self, from: data)
    print("object", object)
} catch {
    print(error)
}

@robertmryan
Copy link
Author

robertmryan commented Nov 9, 2019

Personally, I’d use camelCase:

struct Billing: Codable {
    let firstName: String
    let lastName: String
    let company: String
    let address1: String
    let address2: String
    let city: String
    let state: String
    let postcode: String
    let country: String
    let phone: String
    let email: String
}

struct CouponLines: Codable {
    let subtotal: String
    let productId: Int
    let quantity: Int
}

struct LineItem: Codable {
    let subtotal: String
    let productId: Int
    let quantity: Int
}

struct Setting: Codable {
    let cost: Cost
}

struct Cost: Codable {
    let id: String
    let label: String
    let value: String
}

struct Shipping: Codable {
    let firstName: String
    let lastName: String
    let address1: String
    let address2: String
    let state: String
    let postcode: String
    let country: String
}

struct ShippingLines: Codable {
    let instanceId: Int
    let id: Int
    let title: String
    let methodTitle: String
    let methodId: String
    let settings: Setting
}

struct NewOrder : Codable {
    let billing : Billing
    let couponLines : [CouponLines]?
    let customerId : Int
    let lineItems : [LineItem]
    let paymentMethod : String
    let paymentMethodTitle : String
    let setPaid : Bool
    let shipping : Shipping
    let shippingLines : [ShippingLines]
}

And then let JSONDecoder convert the keys for me:

let data = #"{"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":"محسن"}}"#
    .data(using: .utf8)!

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    let object = try decoder.decode(NewOrder.self, from: data)
    print("object", object)
} catch {
    print(error)
}

@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