Sometimes my JSON response is like this
{
"products": [
{
"pId": "3564225",
"name": "Maxi Skirt",
"slug": "maxi-skirt",
"sku": "s-navy",
"priority": 10,
"images": [
]
},
{
"pId": "299328304",
"name": "Necklace Setjewellery",
"slug": "american-diamond-necklace-setjewellery",
"sku": "free-size-purple",
"priority": 10,
"images": [
]
}],
"total": 2
}
And Sometimes it looks like this
{
"products": [
],
"total": 0
}
Swift decoder throws following error when parsing empty array response
"*Swift.DecodingError.Context(codingPath: [], debugDescription:
"The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840
"JSON text did not start with array or object and option to allow fragments not set."
UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}*"
How do I write a swift codable struct to handle multiple response JSON like these above?
PS: I can't change anything from server side.
Both JSON objects are valid and can be decoded into
struct Root: Decodable {
let products : [Product]
let total: Int
}
struct Product: Decodable {
let pId, name, slug, sku : String
let priority : Int
let images : [Image]
}
struct Image: Decodable {
let url : URL
}
As images is empty I just assume that there is an URL. Change it to the real property name(s) and type(s)
Just refer the following link...
https://medium.com/xcblog/painless-json-parsing-with-swift-codable-2c0beaeb21c1
You will get all the stuffs about your issues and more informations in order to decrease further issues and improve your code structure.
Related
So this is the response structure that I have.
The same identifier attribute, can have a data associated with it that can be a list of other objects.
The outermost table is list of other view types and the inner table is list of rows, if that makes sense.
{
"identifier": "table",
"data": [
{
"identifier": "top_view",
"data": [
{
"value": "this is top header type view"
}
]
},
{
"identifier": "table",
"data": [
{
"value": "this is a first row in a table"
},
{
"value": "this is a second row in a table"
},
{
"value": "this is a third row in a table"
}
]
},
{
"identifier": "bottom_view",
"data": [
{
"value": "this is a footer type view"
}
]
}
]
}
Can I use swifts Codable to decode this in anyway? Solutions for this type of decoding generally involve having an enum around differentdata and using this to inject the correct type associated with it. But in this case, the identifier is the same.
Let me know if I need to add in more details.
Edit 1 -
Well, I am trying to build a new app that has a backend driven UI.
This is just the response of a screen within the app.
To explain more about the json - The outermost table is a tableview that can have 3 cells - the first one being the top header, second a table(that again has 3 cells that each consists of label) and the third is a bootom footer (Not to be confused with a tableviews default header footer).
I understand that the json might be flawed in itself but initially I had hoped that it would work by employing a nested json structure (hence the use of same data key)
The value of data in this case can change across different components.
I think I understand what you are trying to achieve (from what you were saying about the enum). This is something to get you started --
struct TableableData: Codable {
enum ViewType {
case top(values: [String])
case bottom(values: [String])
case table(data: [TableableData])
init?(identifier: String?, data: [TableableData]) {
switch identifier {
case "top_view": self = .top(values: data.compactMap{ $0.value })
case "bottom_view": self = .top(values: data.compactMap{ $0.value })
case "table": self = .table(data: data)
default: return nil
}
}
}
let identifier: String?
let value: String?
let data: [TableableData]!
var view: ViewType? {
ViewType(identifier: identifier, data: data)
}
}
I had to make all fields optional to quickly test it against your json, it works. I would suggest using init from decoder to replace optional with empty array or something.
Cosider an API which reply is always of this structure:
{
"pagination": {
"limit": int,
"offset": int,
"count": int,
"total": int
},
"data": [
{...some obj...}
]
}
So payloads differ only in structure of data objects.
Ideally I'd like to tell F# that all types built from samples have some common part - pagination info, so I can have one generic method which reads all pages.
Is it possible, or do I have to extract pagination object and data array separately with two type providers? I see the benefit of having one provider per response body as it supports reading data from the stream.
I would define two different provided types, one for parsing the pagination data and one for parsing the actual data, i.e. something like this:
type Pagination = JsonProvider<"""{
"pagination": { "limit": 1, "offset": 2,
"count": 3, "total": 4 }
}""">
type OneDataType = JsonProvider<"""{
"data": [ {"a": 1} ]
}""">
If you want to avoid parsing the same JSON file twice (e.g. by calling Pagination.Parse and OneDataType.Parse on the same string), you can actually just parse the data once and then pass the parsed JsonValue to the other type:
let odt = OneDataType.Load("/some/file")
let pg = Pagination.Root(odt.JsonValue)
pg.Pagination.Count
If you wanted to do this with a single provided type, then you could define multiple different fields for the multiple different types of data - but you'd have to name those differently. You'd then need to do some fiddling to read the data correctly. I would not do this, because I find it confusing, but it would look something like this:
type AnyData = JsonProvider<"""{
"pagination": { "limit": 1, "offset": 2,
"count": 3, "total": 4 },
"data": [],
"one_data_type": [ {"a":1} ],
"another_data_type": [ {"b":"xx" }]
}""">
let a = AnyData.Load("/some/file")
// Access pagination data
a.Pagination
// Access data as if it was OneDataType
let oneData = [| for d in a.Data ->
AnyData.OneDataType(d.JsonValue) |]
This question already has an answer here:
How can I decode a JSON response with an unknown key in Swift?
(1 answer)
Closed 3 years ago.
The JSON Data from API contains price for bitcoin in various currencies. I need to modify my struct at runtime, so that it matches up with the keys in data.
Init, Type alias, and Generics Don't work!
'''
struct Model {
var content : intel
}
struct intel {
let last : Float
let averages : day
let timestamp : Int64
let bid : Float
let ask : Float
}
struct day {
let day : Float
}
//Intend to change literal value of content at runtime
//Sample Data!
{
"BTCEUR": {
"last": 9477.207190353169,
"averages": {
"day": 8913.97014278919
},
"timestamp": 1561195263,
"bid": 9473.843088382904,
"ask": 9477.676160131046
},
"BTCUSD": {
"last": 10791.221908483476,
"averages": {
"day": 10149.89204777
},
"timestamp": 1561195263,
"bid": 10787.391373795912,
"ask": 10791.755900918146
}
}
'''
Trying to change the name of a property at runtime is the wrong way to solve this problem. A much better solution is to just decode your JSON into a dictionary.
We can create a typealias like this:
typealias BitcoinData = [String: intel]
and decode the data like this:
let decoder = JSONDecoder()
let bitcoinData = try! decoder.decode(BitcoinData.self, from: data)
print(bitcoinData["BTCUSD"]!.averages.day) // 10149.892
Note how we access BTCUSD using the subscript syntax.
In fact, your JSON structure is quite awkward to decode. As you can see, we had to decode to a dictionary here. It would be much better if you could change the JSON structure.
I have following REST API response:
"items":
[
{
"empid": "1234",
"name": "Santosh",
"hiredby": "Mark",
"date": "2017-01-31,00:19:41 PST",
},
{
"empid": "5678",
"name": "Kumar",
"hiredby": "Bob",
"date": "2017-01-31,08:30:31 PST"
}
]
My query is : - How do i get empid based on querying name as Kumar.
For example: I need to find "Kumar" name and get his empid. (that is, search by name and get his empid as response) I'm able to get the response and store it in Response object. but, from response object how can i traverse and query to get the required value.
Also,
I tried by retrieving as:
String name = get(REST_ENDPOINT).then().body("items.name",hasItems("Kumar")).extract().path("items.empid").toString();
when i print the response i get collection of the empid like [1234,5678], where as my expectation is to get only 5678.
Do I need to parse via JSONArray and JSONObject and iterate the response?
Please suggest.
You can use something like this
response1.jsonPath().getList("collect { it.credentials.findAll { it.credentialType == 'Ban User Name'}.credentialId }.flatten()")
I have given JSON and cannot parse partial data. It seems dictionary into dictionary:
{
"products": [
{
"id": 6796,
"title": "my title",
"description": "desc",
"code": "12345",
"valueType": "null",
"discounts": [
{
"minPrice": null,
"maxPrice": null,
"value": 20,
"avail": false
}
]
}
]
}
I am using the latest version of RESTKit but I cannot properly parse under discounts.
my RestKit settings are:
responseMapping.addAttributeMappingsFromDictionary([
"id" : "id",
"code" : "code",
"title" : "title",
"valueType" : "valueType",
"description" : "desc",
"discounts.minPrice" : "minPrice",
"discounts.maxPrice" : "maxPrice",
"discounts.value" : "value",
"discounts.avail" : "avail",
])
but all values below discounts always return Nil. What I am doing wrong?
You can't directly map using discounts.XXX because discounts is an array and you have no way to index into that array and extract a single value.
You either need to change the source JSON to compress the values out of the dictionary, or create a custom object that you can map each item in the discounts array to.
Technically you could map the whole discounts array, which would give you an array of dictionaries, that you could then unpack in the setter method, but the array of custom objects is usually a better approach.