Format JSON string to iOS Dictionary string with = - ios

First off, what do we call a dictionary with a format like this in iOS?
(
{
name = "Apple";
value = "fruit-1";
},
{
name = "Banana";
value = "fruit-2";
}
)
And for my main question. I somehow need to format a string of JSON, like this:
[{"name":"Apple","value":"fruit-1"},{"name":"Banana","value":"fruit-2"}]
into whatever that format is called (of the string above).
For context, the existing approach of my project uses CoreData where the Server response (which uses the mystery format above) gets saved locally as a String, and I want to follow that format.
EDIT: for more context, I really need to just get the first format into the database because a module of a project was built to read the data with that format (e.g. make use of NSString.propertyList()).
Using a library called ios hierarchy viewer, I can see the saved object in the device.
Original format, server json to db (core data) in Objective-C:
What I've been trying to do in Swift, server json to local using JSONSerialization:

First off, what do we call a dictionary with a format like this in iOS?
According to the documentation of NSString.propertyList(), that's a "text representation of a property list".
It's a wonky, non-standard pretty-printing obtained by calling NSArray.description or NSDictionary.description.
Here's an example that shows a round-trip of data:
// The opening `{` indentation is fucky, but that's how it's generated.
let inputPropertyList = """
(
{
name = "Apple";
value = "fruit-1";
},
{
name = "Banana";
value = "fruit-2";
}
)
"""
// The result is an `Any` because we don't know if the root structure
// of the property list is an array or a dictionary
let deserialized: Any = inputPropertyList.propertyList()
// If you want the description in the same format, you need to cast to
// Foundation.NSArray or Foundation.NSDictionary.
// Swift.Array and Swift.Dictionary have a different description format.
let nsDict = deserialized as! NSArray
let roundTrippedPropertyList = nsDict.description
print(roundTrippedPropertyList)
assert(roundTrippedPropertyList == inputPropertyList)

The second format you show is what you get when you display an object in the debug console. That's the output of the object's description property. It isn't a "JSON string", exactly.
If you want to convert your objets to a true JSON string, see below.
As Alexander pointed out, the first string in your question is the output from NSString's propertyList() function. The format looks quite similar to "pretty-printed" JSON, but it's different enough that it it won't work that way.
The `propertyList() function is a debugging-only function, and I don't know of an existing way to parse that back into objects. If that is the string that's being sent by your server, your server is broken. If that's what you see in core data when you log the contents of a field, it's probably a misunderstanding on your part.
To convert an object to pretty JSON, see this answer, where I created an extension to the Encodable format that implements a property "prettyJSON":
extension Encodable {
var prettyJSON: String {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
guard let data = try? encoder.encode(self),
let output = String(data: data, encoding: .utf8)
else { return "Error converting \(self) to JSON string" }
return output
}
}
That should work for any object that supports the Encodable protocol. (And your object should.)

Related

Is it possible to force encode Date as Date type instead of string?

I am trying to mock Apollo Queries using its init. It pretty much is taking in a dictionary to build the object up.
public init(unsafeResultMap: [String: Any]) {
self.resultMap = unsafeResultMap
}
So, I have decided to create Mock objects that have the same properties of the query objects while being Encodable (So we get the free JSON conversion, which can be represented as a string version dictionary).
For example:
class MockAnimalObject: Encodable {
let teeth: MockTeethObject
init(teeth: MockTeethObject) {
self.teeth = teeth
}
}
class MockTeethObject: Encodable {
let numberOfTeeth: Int
let dateOfTeethCreation: Date
init (numberOfTeeth: Int, dateOfTeethCreation: Date) {
self.numberOfTeeth = numberOfTeeth
self.dateOfTeethCreation = dateOfTeethCreation
}
}
The problem is, the Apollo conversion checks the types during the result map, which in our case is a string of [String: Encodable].
And this is where the Date encodable becomes a problem.
/// This property is auto-generated and not feasible to be edited
/// Teeth date for these teeth
public var teethCreationDate: Date {
get {
// This is the problem. resultMap["teethCreationDate"] is never going to be a Date object since it is encoded.
return resultMap["teethCreationDate"]! as! Date
}
set {
resultMap.updateValue(newValue, forKey: "teethCreationDate")
}
}
So, I am wondering if it is possible to override the encoder to manually set the date value as a custom type.
var container = encoder.singleValueContainer()
try container.encode(date) as Date // Something where I force it to be a non-encodable object
JSON has nothing to do with this. JSON is not any kind of dictionary. It's a serialization format. But you don't want a serialization format. You want to convert types to an Apollo ResultMap, which is [String: Any?]. What you want is a "ResultMapEncoder," not a JSONEncoder.
That's definitely possible. It's just an obnoxious amount of code because Encoder is such a pain to conform to. My first pass is a bit over 600 lines. I could probably strip it down more and it's barely tested, so I don't know if this code works in all (or even most) cases, but it's a start and shows how you would attack this problem.
The starting point is the source code for JSONEncoder. Like sculpture, you start with a giant block of material, and keep removing everything that doesn't look like what you want. Again, this is very, very lightly tested. It basically does what you describe in your question, and not much else is tested.
let animal = MockAnimalObject(teeth: MockTeethObject(numberOfTeeth: 10,
dateOfTeethCreation: .now))
let result = try AnyEncoder().encode(animal)
print(result)
//["teeth": Optional(["dateOfTeethCreation": Optional(2022-08-12 18:35:27 +0000),
// "numberOfTeeth": Optional(10)])]
The key changes, and where you'd want to explore further to make this work the way you want, are:
Gets rid of all configuration and auto-conversions (like snake case)
Handles the "special cases" (Date, Decimal, [String: Encodable]) by just returning them. See wrapEncodable and wrapUntyped
If you want [String: Any] rather than [String: Any?] (which is what ResultMap is), then you can tweak the types a bit. The only tricky piece is you would need to store something like nil as Any? as Any in order to encode nil (or you could encode NSNull, or you could just not encode it at all if you wanted).
Note that this actually returns Any, since it can't know that the top level encodes an object. So you'll need to as? cast it to [String: Any?].
To your question about using Mirror, the good thing about Mirror is that the code is short. The bad thing is that mirror is very slow. So it depends on how important that is. Not everything has the mirror you expect, however. For your purposes, Date has a "struct-like" Mirror, so you have to special-case it. But it's not that hard to write the code. Something like this:
func resultMap(from object: Any) -> Any {
// First handle special cases that aren't what they seem
if object is Date || object is Decimal {
return object
}
let mirror = Mirror(reflecting: object)
switch mirror.displayStyle {
case .some(.struct), .some(.class), .some(.dictionary):
var keyValues: [String: Any] = [:]
for child in mirror.children {
if let label = child.label {
keyValues[label] = resultMap(from: child.value)
}
}
return keyValues
case .some(.collection):
var values: [Any] = []
for child in mirror.children {
values.append(resultMap(from: child.value))
}
return values
default:
return object
}
}
let animal = MockAnimalObject(teeth: MockTeethObject(numberOfTeeth: 10, dateOfTeethCreation: .now))
let result = resultMap(from: animal)
print(result)
// ["teeth": ["dateOfTeethCreation": 2022-08-12 21:08:11 +0000, "numberOfTeeth": 10]]
This time I didn't bother with Any?, but you could probably expand it that way if you needed. You'd need to decide what you'd want to do about enums, tuples, and anything else you'd want to handle specially, but it's pretty flexible. Just slow.
As pointed out in comments, JSON (Javascript object notation) is universal format and is not anyhow related to Date object in Swift after it is encoded. Therefore somewhere in the flow you need to make it String Double or some other object type that can be encoded to JSON. Anyway, if you want to make encoding Date to be easier you can take hold of some native JSONEncoder functions, such as folowing:
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601

Swift: Converting from _PFEncodedString to String (export of Core Data attributes using JSONEncoder)

I’m coding Swift for iOS 11, and in some context (described in part 2) I end up with a variable of type String. However, when examining the variable in Xcode’s debugger the type is shown as _PFEncodedString (which I have read elsewhere is an internal subclass of String). For some reason (described below, but not directly related to the first part of my question) I want to transform the _PFEncodedString variable to a plain String variable. The only way I’ve found to do that is as follows:
var attributeName : String
// ... attributeName is assigned a value and ends up being _PFEncodedString
let data = attributeName.data(using: String.Encoding.utf8)!
let plainName = String(data: data, encoding: String.Encoding.utf8)! // this is type String not _PFEncodedString
The first part of my question is: Is this conversion safe or is there is simpler or better way to do it?
Part 2. For the second part of my question I will first describe the context where the above becomes an issue. I’m using Core Data and want to export attribute names (and values) to JSON. Using reflection I can find Core Data entity names and find the attribute names that should be exported to JSON. The problem is that when I use JSONEncoder with this data, the generated JSON contains Chinese characters whereas the Core Data model attribute names do not. I have found that JSONEncoder produces partly Chinese output when Strings in the exported object is of type _PFEncodedString, but works fine when handling "plain" String types. Therefore the first part of my questions.
Here is code that illustrates the problem, boiled down to a near minimum. QPTVideo is a subclass of NSManagedObject with two attributes ‘qaUUID’ and ‘thumbnailData’.
// First define a struct to be used with JSONEncoder
struct AttributeForJSON : Codable {
var name : String
}
let entityDescription = QPTVideo.entity()
let attributes = entityDescription.attributesByName // this is an array of (key: String, value: NSAttributeDescription).
var allAttributes : [AttributeForJSON] = [] // this will be an array of all the attribute names for export to JSON.
for attribute in attributes {
let attributeName = attribute.key // NOTE: debugger shows this as: attributeName String class name = _PFEncodedString
let aj = AttributeForJSON(name: attributeName)
print(aj.name) // this works just fine, printing “qaUUID” or “thumbnailData”
allAttributes.append(aj) // when exported, the attribute names look nothing like they should, containing Chinese characters.
// now use the work-around from first part of my question:
let data = attributeName.data(using: String.Encoding.utf8)!
let plainName = String(data: data, encoding: String.Encoding.utf8)! // NOTE: debugger shows this as: plainName String "thumbnailData"
let ajplain = AttributeForJSON(name: plainName)
allAttributes.append(ajplain) // when exported, the attribute names are correct.
}
// the rest is just use of JSONEncoder to generate the JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let data = try encoder.encode(allAttributes)
try data.write(to: URL(fileURLWithPath: “/Users/someone/so.json"), options: Data.WritingOptions.atomic)
} catch {
print(error.localizedDescription)
}
The exported JSON looks like this (where I would have expected both of the first two name values to be "qaUUID", and the next two to be "thumbnailData"):
[
{
"name" : "慱啕䑉\u0000戨্"
},
{
"name" : "qaUUID"
},
{
"name" : "桴浵湢楡䑬瑡a\u0000戨্\u0001\u0000\u0000"
},
{
"name" : "thumbnailData"
}
]
So, my here question is: Why do I get Chinese characters? What am I doing wrong in how I use the Core Data reflection API and/or JSONEncoder? How do I do this without resorting to the work-around from the first part of my question?

Use JSONAssert on part of JSON with RESTAssured

I am able to retrieve JSON from a service using RESTAssured.
I would like to use the JSONPath capability to extract JSON and then compare it using JSONAssert.
Here's my example:
#Test
public void doAPITestExample() throws JSONException {
// retrieve JSON from service
Response response = RestAssured.given().get("http://localhost:8081/mockservice");
response.then().assertThat().statusCode(200);
String body = response.getBody().asString();
System.out.println("Body:" + body);
/*
{"datetime": "2018-06-21 17:48:07.488384", "data": [{"foo": "bar"}, {"test": "this is test data"}]}
*/
// able to compare entire body with JSONAssert, strict=false
Object data = response.then().extract().path("data");
System.out.println("Response data:");
System.out.println(data.getClass()); // class java.util.ArrayList
System.out.println(data.toString());
// JSONAssert data with strict=false
String expectedJSON = "{\"data\":[{\"foo\": \"bar\"}, {\"test\": \"this is test data\"}]}";
JSONAssert.assertEquals(expectedJSON, response.getBody().asString(), false);
// How do I extract JSON with JSONPath, use JSONAssert together?
}
Approach 1 - using JSONPath to get JSONObject
How do I get JSONPath to return a JSONObject that can be used by JSONAssert?
In the code example:
Object data = response.then().extract().path("data");
This returns a java.util.ArrayList. How can this be used with JSONAssert to compare to expected JSON?
Approach 2 - parse extracted data with JSONParser
If I do data.toString(), this returns a string that cannot be parsed due to lack of quote handling for string values with spaces strings:
String dataString = response.then().extract().path("data").toString();
JSONArray dataArray = (JSONArray) JSONParser.parseJSON(dataString);
Result:
org.json.JSONException: Unterminated object at character 24 of [{foo=bar}, {test=this is test data}]
Approach 3 - Using JSON schema validation
Is is possible to extract just the data property from the JSON and run that against JSON Schema on just that part?
Note: the entire JSON that is returned is quite large. I just want to validate the data property from it.
What would an example of doing a JSON schema validation look for just the data property from the JSON?
Thanks;
You can use the method jsonPath in your response object.
Example:
// this will return bar as per your example response.
String foo = response.jsonPath ().getString ("data[0].foo");
For more info about JSon path check here.

iOS 9 JSON Parsing loop

I'm creating an app that should retrieve some JSON from a database.
This is how my JSON looks:
[{"id":"1","longitude":"10","latitude":"10","visibility":"5","timestampAdded":"2015-10-01 15:01:39"},{"id":"2","longitude":"15","latitude":"15","visibility":"5","timestampAdded":"2015-10-01 15:06:25"}]
And this is the code i use:
if let jsonResult = JSON as? Array<Dictionary<String,String>> {
let longitudeValue = jsonResult[0]["longitude"]
let latitudeValue = jsonResult[0]["latitude"]
let visibilityValue = jsonResult[0]["visibility"]
print(longitudeValue!)
print(latitudeValue!)
print(visibilityValue!)
}
As you can see it only gets the first chunk from the JSON and if there are no JSON at all it will crash, but if i want it to count the amount and make an array out of it like this:
var longitudeArray = [10, 15]
var latitudeArray = [10, 15]
And so on...
I also need this to be apple watch compatible so i can't use SwiftyJSON.
What do i do? I really hope you can help me!
Thanks.
SOLVED!
Problems was solved by "Eric D."
This is the code:
do {
if let url = NSURL(string: "YOU URL HERE"),
let data = NSData(contentsOfURL: url),
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [[String:AnyObject]] {
print(jsonResult)
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
print(longitudeArray)
print(latitudeArray)
}
} catch let error as NSError {
print(error.description)
}
Thank you soo much Eric!! :-)
You could use flatMap to get an array of your elements:
let longitudeArray = jsonResult.flatMap { $0["longitude"] as? String }
let latitudeArray = jsonResult.flatMap { $0["latitude"] as? String }
etc.
flatMap is like map but unwraps optionals, which is adequate because we need to safely cast the type of the object we get from each dictionary in the json array.
$0 represents the object in the current iteration of flatMap of the array it's applied to.
If you're currently using SwiftyJSON, then that would be:
let longitudeArray = jsonResult.flatMap { $1["longitude"].string }
let latitudeArray = jsonResult.flatMap { $1["latitude"].string }
because .string is SwiftyJSON's optional String value getter.
But as you said, you don't want to use it (anymore), so you need to use NSJSONSerialization to decode your JSON data, there's plenty of examples on the Web and on SO. Then you will be able to use my original answer.
You're already getting an array with all of the elements (not just the first one. you're simply only accessing the first one). jsonResult is an array of dictionaries. Each dictionary (in this case, based on the json you provided) contains these elements: id, longitude, latitude, visibility and timestampAdded. In order to access each of them, you can simply loop over jsonResult and access the i'th element (and not always the 0 element). This will also prevent the crash you're experiencing with the json is blank or invalid (since you'll only be going over the valid elements in jsonResult.
This will give you the flexibility to create the custom arrays you wish to create (in order to create an array of all of the longitudes, for example, you will simply add that element to the new array while looping over jsonResult). However, if you'd like to save yourself the trouble of manually building these arrays and assuming you have control over the json structure, I would recommend changing the received json to the relevant structure (a dictionary or arrays instead of an array of dictionaries), so it would better fit your needs and provide you the results in the relevant format right "out of the box".

iOS, Swift - JSON Response. VK SDK

I am using the VK SDK for iOS, and I need to take out the images URLs from JSON response from VK.
The response is look like this:
The actual response could be AnyObject OR String, and I only need the largest image URL (photo_1280) as string.
In the response comes 1-10 photos and which parameters I will need to change to get the particular photo like first or second and so on.
I'm using Swift in my project but can understand Objective-C.
JSON is just a format that lets you exchange information between languages (or objects).
You need to 'parse' the string into a JSON object. Its a little different for each language. For example on iOS I create my UI elements from a JSON file where I load the file and create a dictionary object from it. In your case you are doing it from a string. You need to turn that string into a valid object in your language. In Swift I prefer a Dictionary. So I use a typealias for that and cast the nsDictionary as that type of object.
Then to access objects, I access keys in the dictionary. In your case you would create a dictionary object too, and access the "attachments" object, which in your case is an array so you'll need to do extra processing on it to get each image (i.e. go through the array). This should get you going.
typealias Dict = Dictionary<String,AnyObject>
func loadDictionaryFromJSON(jsonString:String) -> Dict
{
var JSONData:NSData! = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
var JSONError:NSError?
let swiftObject:AnyObject = NSJSONSerialization.JSONObjectWithData(JSONData, options: NSJSONReadingOptions.AllowFragments, error: &JSONError)!
if let nsDictionaryObject = swiftObject as? NSDictionary
{
if let dictionaryObject = nsDictionaryObject as Dictionary?
{
return dictionaryObject as Dict
}else
{
println("Error could not make dictionary from NSDictionary in \(self)")
}
}else
{
"Error could not make NSDictionary in \(self)"
}
println("Empty dictionary passed, fix it!")
return Dict()
}
Now to access things you just do
var objects:Dictionary<String,AnyObject> = loadDictionaryFromJSON("{"what":"ever"}")
var whatever = objects["what"]

Resources