Updating values within array of dictionaries using Swift - json

I am teaching myself Swift these days and have trouble understanding how to update values in dictionaries stored in an array. The array consists of dictionaries read from file using JSON (I am using SwiftyJSON). The JSON file looks like this:
{
"test2" : [
{
"files" : "203",
"url" : "Optional(\"\/Users\/heisan\/test\/\")",
"date" : "April 6, 2015 at 15:23:40 EDT"
}
]
}
Each key:value pair in the dictionaries consists of String:Array where the Array again consists of String:String dictionaries. My function for updating the content for specific key looks like this:
#objc func addOptionForKey(key: String, date: NSDate, url: NSURL, files: NSNumber)-> Bool {
let dictData: NSData? = self.createDataOfDictionary()
if (dictData != nil) {
let json = JSON(data: dictData!)
for (localkey: String, var subJson: JSON) in json {
var backups: Array = [Dictionary<String,AnyObject>]()
var restores = subJson.arrayValue
for restore in restores {
let innerDict: [String:String] = ["url":restore["url"].stringValue,
"files":restore["files"].stringValue,
"date":restore["date"].stringValue]
backups.append(innerDict)
}
self.restoreDict[localkey]=backups
}
return true
}
Here, a the restoreDict property is defined as
self.restoreDict = String:[AnyObject]
The function crashes when I try to update my array for the specific key:
self.restoreDict[localkey]=backups
I am sure I am doing some amateur error here but a kick in the right direction would be greatly appreciated. Thanks. T
Update: My problem turned out to be related to my version of Xcode and Swift. When I switched to Xcode 6.3 + Swift 1.2 as well as moving away from using swiftJSON (as Swift 1.2 has many improvements for handling JSON) everything works as expected.

var restoreDict = [String:[String:AnyObject]]()
let test2 = ["files" : 203, "url" : "blah", "date" : "April 6, 2015 at 15:23:40 EDT"]
restoreDict["local"] = test2
println(restoreDict)
// prints "[local: [files: 203, url: blah, date: April 6, 2015 at 15:23:40 EDT]]"

Related

Decoding JSON from Bing search API

I'm trying to use the BingAPI in Swift which has no guide or directions. I'm so close but I can't figure out what type is webpages (
_type and query context are in the correct format, but I don't know how to write webPages.)
error code:
"typeMismatch(Swift.Dictionary<Swift.String, Swift.String>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "webPages", intValue: nil), _JSONKey(stringValue: "value", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, String> but found an array instead.", underlyingError: nil))"
Swift
struct codableData: Codable {
var _type: String
var queryContext: [String : String]
var webPages : [String : [String : String]] // I know it's not right, but here is the problem
}
json results
{
"_type": "SearchResponse",
"queryContext": {
"originalQuery": ""
},
"webPages": {
"totalEstimatedMatches": 20600000,
"value": [
{
"id": "https://api.bing.microsoft.com/api/v7/#WebPages.8",
"name": "tafeqld.edu.au",
"url": "https://tafeqld.edu.au/courses/18106/",
"isFamilyFriendly": true,
"displayUrl": "https://tafeqld.edu.au/courses/18106",
"snippet": "Moved Permanently. The document has moved here.",
"dateLastCrawled": "2023-01-02T12:02:00.0000000Z",
"language": "en",
"isNavigational": false
}
],
"someResultsRemoved": true
},
"rankingResponse": {
"mainline": {
"items": [
{
"answerType": "WebPages",
"resultIndex": 0,
"value": {
"id": "https://api.bing.microsoft.com/api/v7/#WebPages.0"
}
}
]
}
}
}
webPages is not [String: [String: String]] as the value types inside it include numbers as well as other objects which are not simple [String: String] dictionaries either. Like the error is telling you, value is an array and you're trying to decode it as a dictionary.
You could simply change it to [String: Any].
But you'll also benefit from Codable more if you write types matching the structure of the expected JSON.
For example:
struct CodableData: Codable {
let _type: String
let queryContext: QueryContext
let webPages: WebPage
}
struct QueryContext: Codable {
let originalQuery: String
}
struct WebPage: Codable {
let totalEstimatedMatches: Int
let value: [Foo]
let someResultsRemoved: Bool
}
// etc.
Note you only need to define objects and properties for the bits you're interested in.
Notice that the JSON Syntax indicates an Object when it s {} and an Array when it is [].
JSON has a JavaScript origin and stores types from the JavaScript world. JavaScript is not strongly typed and you can have dictionaries and arrays with mixed types.
So in JavaScript to access the WebPages name for example you would do something like BingAPIResponse.webPages.value[0].name and you can do exactly the same in Swift too however you will have to model your Codable struct to match this exact structure with sub-structures because in JavaScript you don't have a guarantee that the array in webPages.value will have all the same types and there is no guarantee that webPages.value[0].name is a string, for example.
You won't be able to use [String : Any] because Any is not decodable and you will get an error if you put values of type Any in you Codable Struct.
This is not a shortcoming of Swift but a result of trying to work with a data structure which doesn't have types in a language with strict types.
So if you want to decode it just using JSONDecoder, you will need to create a Codable struct for each sub object the way #shim described.
Personally I find it too tedious and I built myself a small library to handle JSON, which you can find here: https://github.com/mrtksn/DirectJSON
What this library does is letting you access parts of the JSON before converting them into the type you need. So the type safety is still guaranteed but you can access the data from the JSON using the JavaScript notation without modelling the whole JSON object. It is useful if you are not interested in having the complete model of the JSON object but just want some data from it.
So for example, to access the name of the webpage, you will do:
// add the library to the imports
import DirectJSON
/* Do yourAPI and retrieve the json */
let theJSONResponseString = BingAPICall()
/* Get the part of the data you are interested in */
let name : String? = theJSONResponseString.json.webPages.value[0].name
Note that you can use it with any Codable. So if you are after webPages data, you can have something like:
struct WebPages : Codable {
let id : String
let name : String
let url : String
let isFamilyFriendly : Bool
}
// then simply
let webPages : [WebPages]? = theJSONResponseString.json.webPages.value

Fetch first element in JSON with Swift

I have a JSON string, an example is shown in the screenshot below. How can I print to the console the first element from a given array?
I've tried different options for converting a date to a string, but the string won't let me get the first element in its entirety
I recommend using a package like SwiftyJSON to work with JSON in Swift. You can add it via Swift Package Manager or CocoaPods, whichever you prefer.
Supposing to have this JSON string:
let json = "[{\"id\" : 0, \"text\" : \"hello\"},{\"id\" : 1, \"text\" : \"hi\"}]"
You can parse it as shown, and then retrieve and print to console the first item:
if let data = json.data(using: .utf8) {
if let json = try? JSON(data: data) {
print(json[0])
}
}
This will print on the console as:
{
"text" : "hello",
"id" : 0
}
Remember to import SwiftyJSON at the top of the swift file

How can we format or convert the Xcode log or print in JSON format?

Is there a way we can convert the Xcode print or log in the JSON format?
Currently if we print or log a dictionary or JSON, it shows like this:
data = {
currentCity = Mycity;
friends = 4;
images = (
);
suggestions = 3;
}
but if we want Xcode to print in the proper json format like this, what can we do?
"data": {
"currentCity": "Mycity",
"friends": 4,
"images": [],
"suggestions": 3
}
Or is there any tool available that converts the Xcode print or log in pretty JSON format?
There is a very simpler way in swift
use need to use this pod: SwiftyJSON
Install the pod and use the following code:
import SwiftyJSON
func printInJSON {
var dictionary: [String : Any] = [:]
dictionary["build_number"] = "1.0"
dictionary["data"] = "Print in JSON format on iOS xcode"
// SwiftyJson magic
let json = JSON(dictionary)
print(json)
}
Output:
{
"build_number" : "1.0"
"data" : "Print in JSON format on iOS xcode"
}
If you are printing an object that is coming back from a network request, I find using Charles Proxy is usually easier. It will format it in JSON and it is a very useful debugging tool.
You may try in console:
po print(data)
It usually helped me

Transform string array to integer array during JSON decode using Unbox in Swift

UPDATE: This has now been fixed in Unbox 1.9.
I'm using the awesome Unbox JSON decoder in a small Swift project and so far it has proven very easy to work with. However, I've run into a small problem trying to decode and transform a string array in JSON to an integer array property.
I have a Conversation class that looks like this:
final class Conversation: Unboxable {
var participants: [Int]?
var subject: String?
var timestamp: Int?
var conversationId: String?
init(unboxer: Unboxer) {
self.participants = unboxer.unbox("participants")
self.subject = unboxer.unbox("subject")
self.timestamp = unboxer.unbox("timestamp")
self.conversationId = unboxer.unbox("conversationId")
}
}
The JSON that I'm working with looks like this:
{
"subject" : "subject line goes here",
"participants" : [
"201984",
"237810",
"149092",
"202577"
],
"timestamp" : 1468322845885,
"conversationId" : "9bf356d1-4823-11e6a51e-2d6dfda33d93"
}
As you can see, the participants array in the JSON is a string array and I'd like to transform it into an integer array. When I run my code, the participants property in my class is nil after unboxing. If I change its type to [String]?, then it unboxes ok. One of the nice things about Unbox is that it will automagically transform (where possible) a string value into a numeric type for you. But either I'm doing something wrong or Unbox doesn't provide this same capability for arrays. Has anyone done this using Unbox before?

Nested Arrays throwing error in realm.create(value: JSON) for Swift

I'm using Realm in my Swift project and have a rather long JSON file with a couple of nested properties. I'm aware that in order for Realm to use this serialized JSON data directly, the properties need to match exactly (https://realm.io/docs/swift/latest/#json).
But because Realm Lists need to have an Object instead of a String, I have to use something like List with Requirement being a Realm Object that holds a single String called 'value'.
When I run this code:
try! realm.write {
let json = try! NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let exhibit = Exhibit(value: json)
exhibit.id = "1"
realm.add(exhibit, update: true)
}
I get this error message:
*** Terminating app due to uncaught exception 'RLMException', reason: 'req1' to initialize object of type 'Requirements': missing key 'value''
Here's a shortened version of the JSON I'm feeding in there:
{
"exhibit_name": "test1",
"requirements": [
"req1",
"req2"
],
"geofence": {
"latitude": 36.40599779999999,
"longitude": -105.57696279999999,
"radius": 500
}
}
And my Realm model classes are this:
class Exhibit: Object {
override static func primaryKey() -> String? {
return "id"
}
dynamic var id = "0" //primary key
dynamic var exhibit_name: String = ""
let requirements = List<Requirements>()
dynamic var geofence: Geofence?
}
class Geofence: Object {
dynamic var latitude: Float = 0.0
dynamic var longitude: Float = 0.0
dynamic var radius: Float = 0.0
}
class Requirements: Object {
dynamic var value = ""
}
I find it interesting that I'm not getting any errors for the Geofence property, since that's a dictionary.
How do I set up the Requirements model to make this work properly?
Unfortunately you can't just setup your Requirements model in a different way, which would allow you to directly map your JSON to Realm objects.
The init(value: AnyObject) initializer expects either a dictionary, where the keys are the names of your object properties, or an array, where the property values are ordered in the same like they are defined in your object model. This initializer is recursively called for related objects.
So to make that work, you will need to transform your JSON, so that you nest the string values into either dictionaries or arrays. In your specific case you could achieve that like seen below:
…
var jsonDict = json as! [String : AnyObject]
jsonDict["requirements"] = jsonDict["requirements"].map { ["value": $0] }
let exhibit = Exhibit(value: jsonDict)
…
Side Note
I'd recommend using singular names for your Realm model object classes (here Requirement instead Requirements) as each object just represent a single entity, even if you use them only in to-many relationships.