I have a JSON response that looks something like this:
{
"persons": {
"John": {
"name": "John",
"age": 24
},
"Michael": {
"name": "Michael",
"age": 44
},
"Jack": {
"name": "Jack",
"age": 25
}
}
}
As you can see this could be parse with a struct that looks like this:
struct PersonsResponse: Decodable {
let persons: [String: Person]
}
struct Person: Decodable {
let name: String
let age: Ing
}
However, what this does, is returns a dictionary and parses fine. What I would need is to preserve the order of the persons as they arrive inside this JSON response. I have come across KeyValuePairs in swift which basically are ordered dictionaries but for the love of God I can't figure out how to decode it into being an KeyValuePairs<String, Person>.
Apple documentation says that instantiating a KeyValuePairs object is as easy as doing:
let recordTimes: KeyValuePairs = ["Florence Griffith-Joyner": 10.49,
"Evelyn Ashford": 10.76,
"Evelyn Ashford": 10.79,
"Marlies Gohr": 10.81]
Literally. But when I am decoding my response with:
struct PersonsResponse: Decodable {
let persons: KeyValuePairs<String, Person>
...
...
init(from decoder: Decoder) throws {
...
let personsDictionary = try container.decode([String: Person].self, forKey: .persons)
and then try to do:
persons = personsDictionary
of course it doesn't work at all. I tried to do all kinds of magic already with no luck. Does anyone have any solution to parsing dictionaries into ordered sequences or even Arrays? Thanks for helping!
There is NO WAY the JSON response changes into being an array and YES it always is the same order.
Both Swift, JSON Dictionaries are unordered by there nature. The JSON format does notmaintain key ordering, and as such, does not required parser to preserve the order.
If you need an ordered collection, you its better to returning an array of key-value pairs in the JSON
{
"persons": [
{ "John": {
"name": "John",
"age": 24
}
},
{"Michael": {
"name": "Michael",
"age": 44
}
},
{ "Jack": {
"name": "Jack",
"age": 25
}
}
]
}
Related
I am attempting to find each instance of the string name: being different.
As for the example of JSON below I want to pull Alamo Draft House Lamar and Alamo Draft House Ritz and place them into an array.
JSON:
[{
"tmsId": "MV011110340000",
"rootId": "15444050",
"subType": "Feature Film",
"title": "Bohemian Rhapsody",
"releaseYear": 2018,
"releaseDate": "2018-11-02",
"titleLang": "en",
"descriptionLang": "en",
"entityType": "Movie",
"genres": ["Biography", "Historical drama", "Music"],
"longDescription": "Singer Freddie Mercury, guitarist Brian May, drummer Roger Taylor and bass guitarist John Deacon take the music world by storm when they form the rock 'n' roll band Queen in 1970. Surrounded by darker influences, Mercury decides to leave Queen years later to pursue a solo career. Diagnosed with AIDS in the 1980s, the flamboyant frontman reunites with the group for Live Aid -- leading the band in one of the greatest performances in rock history.",
"shortDescription": "Singer Freddie Mercury of Queen battles personal demons after taking the music world by storm.",
"topCast": ["Rami Malek", "Lucy Boynton", "Gwilym Lee"],
"directors": ["Bryan Singer"],
"officialUrl": "https://www.foxmovies.com/movies/bohemian-rhapsody",
"ratings": [{
"body": "Motion Picture Association of America",
"code": "PG-13"
}],
"advisories": ["Adult Language", "Adult Situations"],
"runTime": "PT02H15M",
"preferredImage": {
"width": "240",
"height": "360",
"uri": "assets/p15444050_v_v5_as.jpg",
"category": "VOD Art",
"text": "yes",
"primary": "true"
},
"showtimes": [{
{
"theatre": {
"id": "9489",
"name": "Alamo Drafthouse at the Ritz"
},
"dateTime": "2018-11-10T19:15",
"barg": false,
"ticketURI": "http://www.fandango.com/tms.asp?t=AAUQP&m=185586&d=2018-11-10"
}, {
"theatre": {
"id": "9489",
"name": "Alamo Drafthouse at the Ritz"
},
"dateTime": "2018-11-10T22:30",
"barg": false,
"ticketURI": "http://www.fandango.com/tms.asp?t=AAUQP&m=185586&d=2018-11-10"
}, {
"theatre": {
"id": "5084",
"name": "Alamo Drafthouse South Lamar"
},
"dateTime": "2018-11-10T12:00",
"barg": false,
"ticketURI": "http://www.fandango.com/tms.asp?t=AATHS&m=185586&d=2018-11-10"
}, {
"theatre": {
"id": "5084",
"name": "Alamo Drafthouse South Lamar"
},
"dateTime": "2018-11-10T15:40",
"barg": false,
"ticketURI": "http://www.fandango.com/tms.asp?t=AATHS&m=185586&d=2018-11-10"
},
}]
}]
Here is my api code:
var shows = [Shows]()
struct Shows: Codable {
let showtimes: [Showtimes]
struct Showtimes: Codable {
let theatre: Theater
struct Theater: Codable {
let id: String
let name: String
}
}
}
func loadShowtimes() {
let apiKey = ""
let today = "2018-11-10"
let zip = "78701"
let filmId = "MV011110340000"
let radius = "15"
let url = URL(string: "http://data.tmsapi.com/v1.1/movies/\(filmId)/showings?startDate=\(today)&numDays=5&zip=\(zip)&radius=\(radius)&api_key=\(apiKey)")
let request = URLRequest(
url: url! as URL,
cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData,
timeoutInterval: 10 )
let session = URLSession (
configuration: URLSessionConfiguration.default,
delegate: nil,
delegateQueue: OperationQueue.main
)
let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
if let data = data {
do { let shows = try! JSONDecoder().decode([Shows].self, from: data)
self.shows = shows
}
}
})
task.resume()
}
How would I approach sorting through the array and finding each instance of name: being different, then take each name and place them into a new array?
There are several ways to iterate through your array of Shows and their array of Theater to get the complete list of names. Once you have the full list of names you can get a unique list of those names.
Here is one approach:
let names = Array(Set(shows.map { $0.showtimes.map { $0.theatre.name }}.reduce([]) { $0 + $1 }))
Let's split that up to better explain what is going on.
let allNames = shows.map { $0.showtimes.map { $0.theatre.name }}.reduce([]) { $0 + $1 }
let uniqueNames = Array(Set(allNames))
The shows.map iterates through each Shows in shows. The inner map in turn iterates each Theatre in each of those Shows returning its name. So the inner map gives an array of names. The first map results in an array of arrays of names. The reduce merges those arrays of names into a single array of names leaving allNames with a single array containing every name.
The use of Array(Set(allNames)) first creates a unique set of the names and then it creates an array from that set.
If you want the final result to be sorted alphabetically then add .sorted() to the end.
If you need to keep the original order you can make use of NSOrderedSet and remove any use of sorted.
let names = NSOrderedSet(array: shows.map { $0.showtimes.map { $0.theatre.name }}.reduce([]) { $0 + $1 }).array as! [String]
This question already has answers here:
Using Codable on a dynamic type/object
(2 answers)
Closed 4 years ago.
I have a json data like this, the root element will be generate by php which data is stored in mysql, and in the future it will be increased or change
{
"Category name 1": [
{
"name": "name 1",
"URL": "http://google.com"
}
],
"Php generated Category name 2": [
{
"name": "name 2",
"URL": "http://google.com"
}
]
}
what I want is i need the category name to be table view section header title so the section row will be listed nicely
however all information that I googled was provided that category name is a fixed name
Thanks in advance
From the data you posted, it looks like you will have this kind of a model:
struct Category: Decodable {
let name: String
let content: [Content]
struct Content: Decodable {
let name: String
let URL: String
}
}
In order to decode the JSON structure to match this model, we will need to do some custom parsing. The first issue we need to address is that we don't know the names of each category in order to parse it, since the key for the category is also the name of it. We will need to introduce a CodingKey that can take any String value, so that it can dynamically load any JSON string key.
/// When encoding/decoding, this struct allows you to dynamically read/create any coding key without knowing the values ahead of time.
struct DynamicCodingKey: CodingKey {
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
We will also need a new type that we can use to handle the custom parsing for the entire JSON list. With that new type, we must implement Decodable.init(from:) to do our custom parsing.
struct CategoryList: Decodable {
let categories: [Category]
// This is the model we created in the first step.
struct Category: Decodable {...}
init(from decoder: Decoder) throws {
// Key the JSON container with our dynamic keys.
let categoriesContainer = try decoder.container(keyedBy: DynamicCodingKey.self)
// The container's keys will be the names of each of the categories.
// We can loop over each key and decode the Content from the JSON for that
// key, then use the key as the name to create our Category.
categories = try categoriesContainer.allKeys.map { key in
let content = try categoriesContainer.decode([Category.Content].self, forKey: key)
return Category(name: key.stringValue, content: content)
}
}
}
With this CategoryList JSON decoding wrapper, we can decode the JSON in a way that fits our model, and use the model to populate the table with sections. Each Category in the CategoryList would be a section, and each Content within the Category would be a row.
let categories = try JSONDecoder().decode(CategoryList.self, from: data).categories
You can try this structure , and set the generated category title inside the key named title
{
"AllCategories": [{
"title":"any1",
"content" : [
{
"name": "name 1",
"URL": "http://google.com"
},
{
"name": "name 1",
"URL": "http://google.com"
}
]
},
{
"title":"any2",
"content" : [
{
"name": "name 1",
"URL": "http://google.com"
},
{
"name": "name 1",
"URL": "http://google.com"
}
]
}
]
}
Suppose i have json string in which there is a json array called data.
The array holds json object of user profile data for example name,age,gender etc.
Now want to parse that json object as per order, for example if the object is
{
"name": "sample name",
"age": "30",
"gender": "male"
}
i want to parse the list as ordered like name,age,gender but with ios,when i convert the json object as dictionary , the order is changed,i know dictionary is not ordered so what is the the alternative to achieve this?
its a third party api so i dont have any hand on it,we have done it in android with linked hash map,but really stuck in swift , the last thing i would want to do is parse with regular expression.
im parsing the json in following way :
var rootData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
if let val = fromList["data"] {
let dataNode = val as! [[String:Any]]
for row in dataNode {
for (key,keyVal) in row {
//here the key is not in order.because when we cast it as dictionary the order gets changed.
}
}
For android we have achieved to do this with following function :
public ArrayList<LinkedHashMap<String, Object>> parseJsonArrayList(String odata, String arrayName) {
ArrayList<LinkedHashMap<String, Object>> mylist = new ArrayList<>();
try {
JSONObject e = new JSONObject(odata);
JSONArray data = e.getJSONArray(arrayName);
for(int i = 0; i < data.length(); ++i) {
JSONObject v = data.getJSONObject(i);
LinkedHashMap<String, Object> map = new LinkedHashMap<>(100, 0.75f, false);
Iterator keys = v.keys();
while(keys.hasNext()) {
String key = String.valueOf(keys.next());
//gph.log("debug4", key);
map.put(key, v.getString(key));
//gph.log("debug4", v.getString(key));
}
mylist.add(map);
}
} catch (JSONException var10) {
var10.printStackTrace();
}
return mylist;
}
Don’t try to order the dictionary. Instead create an array of the keys in the order you desire:
let keys = [“name”, “age”, “gender”]
Then access the dictionary with them:
for key in keys {
let value = dict[key]
// Present the value.
}
That will ensure the order you expect.
As you mentioned you cannot get the ordered data in Dictionary. If possible you can add the "order" key in your JSON like
[
{
"name": "sample name",
"order": 1
},
{
"age": "30",
"order": 1
},
{
"gender": "",
"male": "",
"order": 1
}
]
so that based on the order key you can do the sorting.
I used parsing son in my codes for news api
the son is some thing like this
> {
"status": "ok",
"source": "associated-press",
"sortBy": "top",
-"articles": [
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
"title": "Pro-Trump states most affected by his health care decision",
"description": "President Donald Trump's decision to end a provision of the Affordable Care Act that was benefiting roughly 6 million Americans helps fulfill a campaign promise",
"url": "https:urlexample",
"urlToImage": "url example",
},
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
"title": "Pro-Trump states most affected by his health care decision",
"description": "President Donald Trump's decision to end a provision of the Affordable Care Act that was benefiting roughly 6 million Americans helps fulfill a campaign promise",
"url": "https:urlexample",
"urlToImage": "url example",
},
]
}
as you see in each array we have title - description and more
I want to parse this jason into separated array for example append all of the titles in one array and append all of the descriptions in another ones and more
here is my code
struct Response : Decodable {
let articles: articles
}
struct articles: Decodable {
let title: String
let description : String
let url : String
let urlToImage : String
}
and here is the codes for json
let jsonUrl = "https://newsapi.org/[your codes]"
guard let url = URL(string : jsonUrl) else {
return
}
URLSession.shared.dataTask(with: url) { (data , response , error) in
guard let data = data else {return}
do {
let article = try JSONDecoder().decode(Response.self , from : data)
print(article.articles.title)
print(article.articles.description)
print(article.articles.url)
print(article.articles.urlToImage)
}
catch {
print(error)
}
}.resume()
and when I run this I will receive this error
"Expected to decode Dictionary but found an array instead.", underlyingError: nil))
First, to distinguish between property/method names and type names, try to follow Swift naming conventions: (the following is from Swift API Design guidelines)
Names of types and protocols are UpperCamelCase. Everything else is lowerCamelCase.
Also, your articles struct represents data for just one article, not multiple. So it should start with a capital A, and be singular:
struct Article: Decodable {
Secondly, if you take another look at the JSON you're getting back, articles is an array of dictionaries:
-"articles": [
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
...
},
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
...
},
So the articles property in your Response struct should be an array of Article.
struct Response : Decodable {
let articles: [Article]
}
Here is my Json
{
"id": "63",
"name": "Magnet",
"price": "₹1250",
"description": "",
"image": [
"catalog/IMG-20150119-WA0012_azw1e3ge.jpg",
"catalog/IMG-20150119-WA0029_6mr3ndda.jpg",
"catalog/IMG-20150119-WA0028_ooc2ea52.jpg",
"catalog/IMG-20150119-WA0026_4wjz5882.jpg",
"catalog/IMG-20150119-WA0024_e38xvczi.jpg",
"catalog/IMG-20150119-WA0020_vyzhfkvf.jpg",
"catalog/IMG-20150119-WA0018_u686bmde.jpg",
"catalog/IMG-20150119-WA0016_c8ffp19i.jpg"
],
"thumb_image": [
"cache/catalog/IMG-20150119-WA0012_azw1e3ge-300x412.jpg",
"cache/catalog/IMG-20150119-WA0029_6mr3ndda-300x412.jpg",
"cache/catalog/IMG-20150119-WA0028_ooc2ea52-300x412.jpg",
"cache/catalog/IMG-20150119-WA0026_4wjz5882-300x412.jpg",
"cache/catalog/IMG-20150119-WA0024_e38xvczi-300x412.jpg",
"cache/catalog/IMG-20150119-WA0020_vyzhfkvf-300x412.jpg",
"cache/catalog/IMG-20150119-WA0018_u686bmde-300x412.jpg",
"cache/catalog/IMG-20150119-WA0016_c8ffp19i-300x412.jpg"
],
"specifications": [
{
"Fabrics": [
"Pure chiffon straight cut suits 48" length"
]
},
{
"MOQ": [
"Minimum 10"
]
}
]
}
In above json string the "specification" arraylist has dynamic number of key and each key has dynamic number of values
So how can parse this? Please help if anyone knows this...
Thanks in advance
There are multiple ways to do parsing. In your case specifications should be an array, so you'll be able to loop on each items.
You might want to :
Create your own JSON parse class / methods ;
Use an existing library to parse JSON.
For the second option, you can give a look at the following :
https://github.com/Wolg/awesome-swift#jsonxml-manipulation
var yourJson = data as? NSDictionary
if let id = yourJson.valueForKey("id") as String
{
//save your id from json
}
if let name = yourJson.valueForKey("name") as String
{
//save your name
}
...
if let images = yourJson.valueForKey("image") as NSArray
{
for im in images
{
//save image
}
//the same for all othe images
}
... And so on...
You should also watch some tutorials, to understand the basics of JSON parsing..
https://www.youtube.com/watch?v=MtcscjMxxq4