'Any' is not convertible to '[[String : Any]]' - swift3

How can I cast Any type I'm getting from JSONSerialization to an array of dictionaries? My code is:
let jsonArray: [[AnyHashable: Any]]
do {
jsonArray = try JSONSerialization.jsonObject(with: jsonData, options: [.ReadingOptions.allowFragments]) as! [[AnyHashable : Any]]
}
catch {
let description = NSLocalizedString("Could not analyze earthquake data", comment: "Failed to unpack JSON")
print(description)
return
}
But compiler gives me error message:
'Any' is not convertible to '[[AnyHashable : Any]]'
P.S.
I need to parse array of dictionaries, so JSON file looks like this:
[{
"username": "admin",
"password": "123"
}, {
"username": "bbvb",
"password": "3333"
}, {
"username": "asd",
"password": "222"
}]

why are you using AnyHashable ??
Try this:
let jsonArray: Any? = nil
do {
jsonArray = try JSONSerialization.jsonObject(with: jsonData, options: []) as! [Any]
if jsonArray != nil {
if let resp = jsonArray as? [[AnyHashable : Any]]{
//your result should be here inside resp, which is an array of dictionary of key-val type `AnyHashable : Any`, although you could just use String value for the key, making your format from [[AnyHashable : Any]] to [[String : Any]]
}
}
catch {
let description = NSLocalizedString("Could not analyze earthquake data", comment: "Failed to unpack JSON")
print(description)
return
}

Related

How to manipulate strings and array in Apache Velocity request mapping template in AWS appSync

This is my first time working with VTL, so please correct my code if there is something very silly in it.
What I want to achieve
{
"cities": ["jaipur", "mumbai", "delhi", "sheros", "jalandhar", "bengaluru"]
}
Graphql schema:
type Query{
getCitiesForGroups: String
}
Default response template:
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
Using the default response template, the result I was getting
{ "data": { "getCitiesForGroups": [ "{groupcity=jaipur}", "{groupcity=mumbai}", "{groupcity=delhi}", "{groupcity=sheros}", "{groupcity=jalandhar}", "{groupcity=bengaluru}" ] } }
My request template
{
"version": "2018-05-29",
"statements": [
"select DISTINCT LOWER(city) as city from public.Groups"
]
}
To get the desired output, I changed the response template as I wanted to loop over the response I got from the DB, and remove the {city=} from the string by using the substring method given in AWS resolver mapping docs and this is where I am facing the problem.
My response template
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
#set ($rawListOfCities = $utils.rds.toJsonObject($ctx.result)[0])
#set ($sanitisedListOfCities = [])
#foreach( $city in $rawListOfCities )
#set ($equalToIndex = $city.indexOf("="))
#set ($equalToIndex = $equalToIndex + 1)
#set ($curlyLastIndex = $city.lastIndexOf("}"))
#set ($tempCity = $city.substring($equalToIndex, $curlyLastIndex))
## #set ($tempCity = $city)
$util.qr($sanitisedListOfCities.add($tempCity))
#end
$util.toJson($sanitisedListOfCities)
The response that I am getting:
{
"data": {
"getCitiesForGroups": "[null, null, null, null, null, null]"
}
}
However, when I use the line #set ($tempCity = $city) and comment out the line above it, I get the following response:
{
"data": {
"getCitiesForGroups": "[{city=jaipur}, {city=mumbai}, {city=delhi}, {city=sheros}, {city=jalandhar}, {city=bengaluru}]"
}
}
Which means $city has the value {city=jaipur}, so which I want to sanitize and add it to ($sanitisedListOfCities) and return it as the response.
But I am getting null as the result substring method.
So how can I sanitize the response from DB and return it?
I had a similar issue and this is how I solved it.
First, your Graphql schema should look like,
type Query {
getCitiesForGroups: cityList
}
type cityList {
cities: [String]
}
Your request mapping template,
{
"version": "2018-05-29",
"statements": [
"select DISTINCT LOWER(city) as city from public.Groups"
]
}
And finally your response mapping template
#set($cityList = [])
#set($resMap = {})
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
#foreach($item in $utils.rds.toJsonObject($ctx.result)[0])
$util.qr($cityList.add($item.city))
#end
$util.qr($resMap.put("cities", $cityList))
#return($resMap)
Expected response(complete)
{
"data": {
"getCitiesForGroups": {
"cities": [
"jaipur",
"mumbai",
"delhi",
"sheros",
"jalandhar",
"bengaluru"
]
}
}
}
I hope this helps you.

How do I insert an optional field as null using AppSync Resolvers and Aurora?

I have an optional String field, notes, that is sometimes empty. If it's empty I want to insert null, otherwise I want to insert the string.
Here is my resolver -
{
"version" : "2017-02-28",
"operation": "Invoke",
#set($id = $util.autoId())
#set($notes = $util.defaultIfNullOrEmpty($context.arguments.notes, 'null'))
"payload": {
"sql":"INSERT INTO things VALUES ('$id', :NOTES)",
"variableMapping": {
":NOTES" : $notes
},
"responseSQL": "SELECT * FROM things WHERE id = '$id'"
}
}
With this graphql
mutation CreateThing{
createThing() {
id
notes
}
}
I get -
{
"data": {
"createRoll": {
"id": "6af68989-0bdc-44e2-8558-aeb4c8418e93",
"notes": "null"
}
}
}
when I really want null without the quotes.
And with this graphql -
mutation CreateThing{
createThing(notes: "Here are some notes") {
id
notes
}
}
I get -
{
"data": {
"createThing": {
"id": "6af68989-0bdc-44e2-8558-aeb4c8418e93",
"notes": "Here are some notes"
}
}
}
which is what I want.
How do I get a quoteless null and a quoted string into the same field?
TL;DR you should use $util.toJson() to print the $context.arguments.notes correctly. Replace your $notes assignment with
#set($notes = $util.toJson($util.defaultIfNullOrEmpty($context.arguments.notes, null)))
Explanation:
The reason is VTL prints whatever the toString() method returns and your call to
$util.defaultIfNullOrEmpty($context.arguments.notes, 'null') will return the string "null", which will be printed as "null".
If you replace with $util.defaultIfNullOrEmpty($context.arguments.notes, null) then it will return a null string. However, VTL will print $notes because that is the way it handles null references. In order to print null, which is the valid JSON representation of null, we have to serialize it to JSON. So the correct statement is:
#set($notes = $util.toJson($util.defaultIfNullOrEmpty($context.arguments.notes, null)))
Full test:
I'm assuming you started with the RDS sample provided in the AWS AppSync console and modified it. To reproduce, I updated the content field in the Schema to be nullable:
type Mutation {
...
createPost(author: String!, content: String): Post
...
}
type Post {
id: ID!
author: String!
content: String
views: Int
comments: [Comment]
}
and I modified the posts table schema so content can also be null there: (inside the Lambda function)
function conditionallyCreatePostsTable(connection) {
const createTableSQL = `CREATE TABLE IF NOT EXISTS posts (
id VARCHAR(64) NOT NULL,
author VARCHAR(64) NOT NULL,
content VARCHAR(2048),
views INT NOT NULL,
PRIMARY KEY(id))`;
return executeSQL(connection, createTableSQL);
}
This is the request template for the createPost mutation:
{
"version" : "2017-02-28",
"operation": "Invoke",
#set($id = $util.autoId())
"payload": {
"sql":"INSERT INTO posts VALUES ('$id', :AUTHOR, :CONTENT, 1)",
"variableMapping": {
":AUTHOR" : "$context.arguments.author",
":CONTENT" : $util.toJson($util.defaultIfNullOrEmpty($context.arguments.content, null))
},
"responseSQL": "SELECT id, author, content, views FROM posts WHERE id = '$id'"
}
}
and response template:
$util.toJson($context.result[0])
The following query:
mutation CreatePost {
createPost(author: "Me") {
id
author
content
views
}
}
returns:
{
"data": {
"createPost": {
"id": "b42ee08c-956d-4b89-afda-60fe231e86d7",
"author": "Me",
"content": null,
"views": 1
}
}
}
and
mutation CreatePost {
createPost(author: "Me", content: "content") {
id
author
content
views
}
}
returns
{
"data": {
"createPost": {
"id": "c6af0cbf-cf05-4110-8bc2-833bf9fca9f5",
"author": "Me",
"content": "content",
"views": 1
}
}
}
We were looking into the same issue. For some reason, the accepted answer does not work for us. Maybe because it's a beta feature and there is a new resolver version (2018-05-29 vs 2017-02-28, changes here: Resolver Mapping Template Changelog).
We use this for the time being using NULLIF():
{
"version": "2018-05-29",
"statements": [
"INSERT INTO sales_customers_addresses (`id`, `customerid`, `type`, `company`, `country`, `email`) VALUES (NULL, :CUSTOMERID, :TYPE, NULLIF(:COMPANY, ''), NULLIF(:COUNTRY, ''), :EMAIL)"
],
"variableMap": {
":CUSTOMERID": $customerid,
":TYPE": "$type",
":COMPANY": "$util.defaultIfNullOrEmpty($context.args.address.company, '')",
":COUNTRY": "$util.defaultIfNullOrEmpty($context.args.address.country, '')",
":EMAIL": "$context.args.address.email"
}
}

How to resolve parent to child relationship with AppSync

I have schema looking like below
type Post {
id: ID!
creator: String!
createdAt: String!
like: Int!
dislike: Int!
frozen: Boolean!
revisions:[PostRevision!]
}
type PostRevision {
id: ID!
post: Post!
content: String!
author: String!
createdAt: String!
}
type Mutation {
createPost(postInput: CreatePostInput!): Post
}
I would like to be able to batch insert Post and PostRevision at the same time when i run createPost mutation; however, VTL is giving me a much of hard time.
I have tried below
## Variable Declarations
#set($postId = $util.autoId())
#set($postList = [])
#set($postRevisionList = [])
#set($post = {})
#set($revision = {})
## Initialize Post object
$util.qr($post.put("creator", $ctx.args.postInput.author))
$util.qr($post.put("id", $postId))
$util.qr($post.put("createdAt", $util.time.nowEpochMilliSeconds()))
$util.qr($post.put("like", 0))
$util.qr($post.put("dislike", 0))
$util.qr($post.put("frozen", false))
## Initialize PostRevision object
$util.qr($revision.put("id", $util.autoId()))
$util.qr($revision.put("author", $ctx.args.postInput.author))
$util.qr($revision.put("post", $postId))
$util.qr($revision.put("content", $ctx.args.postInput.content))
$util.qr($revision.put("createdAt", $util.time.nowEpochMilliSeconds()))
## Listify objects
$postList.add($post)
$postRevisionList.add($revision)
{
"version" : "2018-05-29",
"operation" : "BatchPutItem",
"tables" : {
"WHISPR_DEV_PostTable": $util.toJson($postList),
"WHISPR_DEV_PostRevisionTable": $util.toJson($postRevisionList)
}
}
So basically I am reconstructing the document in the resolver of createPost so that I can add Post then also add ID of the post to postReivision However when I run below code
mutation insertPost{
createPost(postInput:{
creator:"name"
content:"value"
}){
id
}
}
I get following error
{
"data": {
"createPost": null
},
"errors": [
{
"path": [
"createPost"
],
"data": null,
"errorType": "MappingTemplate",
"errorInfo": null,
"locations": [
{
"line": 2,
"column": 3,
"sourceName": null
}
],
"message": "Expected JSON object but got BOOLEAN instead."
}
]
}
What am I doing wrong?
I know it would be easier to resolve with lambda function but I do not want to double up the cost for no reason. Any help would be greatly appreciated. Thanks!
If anyone still needs the answer for this (this question is still the #1 google hit for the mentioned error message):
The problem is the return value of the add() method, which returns a boolean value.
To fix this, just wrap the add() methods into $util.qr, as you are already doing for the put() methods:
$util.qr(($postList.add($post))
$util.qr(($postRevisionList.add($revision))
It looks like you are missing a call to $util.dynamodb.toDynamoDBJson which is causing AppSync to try to put plain JSON objects into DynamoDB when DynamoDB requires a DynamoDB specific input structure where each attribute instead of being a plain string like "hello world!" is an object { "S": "hello world!" }. The $util.dynamodb.toDynamoDBJson helper handles this for you for convenience. Can you please try adding the toDynamoDBJson() to these lines:
## Listify objects
$postList.add($util.dynamodb.toDynamoDBJson($post))
$postRevisionList.add($util.dynamodb.toDynamoDBJson($revision))
Hope this helps :)

How to set an Array<Object> inside a Dictionary<String, AnyObject> - Swift3

I'm new in swift. I'm trying to add an Array to a specific key in my Dictionary.
I have the following code:
var myArray : Array<Links> = []
var myDict : Dictionary<String, AnyObject> = [:]
myDict["links"] = myArray as AnyObject? // I need help in this row, It does not work.
This is the Json structure I have in myDict and I'm trying to set:
id : "blabla"
links: [
0: {key1: "a", key2: "b", name: "c", link: "d"}
1: {key1: "e", key2: "f", name: "j", link: "h"}
]
Please, consider I already have all the rest working properly. My only problem is how to add my array in the dictionary as commented in the code above.
My JSON structure:
I hope I could make myself clear enough.
Thank you.
First of all don't cast types up and don't annotate types unless the compiler complains.
Second of all a JSON dictionary is [String:Any] in Swift 3.
Further the recommended syntax to create an empty collection object is
var myDict = Dictionary<String, Any>()
Assuming your array – actually a dictionary – is
let myArray = [
0: ["key1": "a", "key2": "b", "name": "c", "link": "d"],
1: ["key1": "e", "key2": "f", "name": "j", "link": "h"]
]
just assign it:
myDict["links"] = myArray
Even if there is a struct
struct Link {
var key1, key2, name, link : String
}
and the array dictionary is
let linkDictionary = [
0: Link(key1:"a", key2: "b", name: "c", link: "d"),
1: Link(key1:"e", key2: "f", name: "g", link: "h")]
you can assign it if the value type is Any
myDict["links"] = linkDictionary
Assuming, for a second, that links really was an array, it would be:
var dictionary: [String: Any] = [
"id": "blabla",
"links": [
["key1": "a", "key2": "b", "name": "c", "link": "d"],
["key1": "e", "key2": "f", "name": "j", "link": "h"]
]
]
// retrieve links, or initialize it if not found
var links = dictionary["links"] as? [[String: String]] ?? [[String: String]]()
// add your new link to local dictionary
links.append(["key1": "k", "key2": "l", "name": "m", "link": "n"])
// update original structure
dictionary["links"] = links
Personally, though, when I see a repeating dictionary structure like your links, this screams for a real model for these objects. For example:
struct Foo {
let id: String
var links: [Link]?
}
struct Link {
let key1: String
let key2: String
let name: String
let link: String
}
var foo = Foo(id: "blabla", links: [
Link(key1: "a", key2: "b", name: "c", link: "d"),
Link(key1: "e", key2: "f", name: "j", link: "h")
])
foo.links?.append(Link(key1: "k", key2: "l", name: "m", link: "n"))
Now, in this latter example, I assumed that links was really an array, not a dictionary, but that's not really my point here. My key observation is that code is more readable and robust if you use proper custom types rather than just arrays and dictionaries.
And if you need to send and receive these model objects to some web service, you then map this model object to and from JSON. But use custom types for your actual model.
If you want to make the above types easily converted to and from JSON, you can use one of the object mapping libraries out there, so you can do something yourself, e.g.:
protocol Jsonable {
var jsonObject: Any { get }
init?(jsonObject: Any)
}
extension Foo: Jsonable {
var jsonObject: Any {
return [
"id": id,
"links": links?.map { $0.jsonObject } ?? [Any]()
]
}
init?(jsonObject: Any) {
guard let dictionary = jsonObject as? [String: Any],
let id = dictionary["id"] as? String else { return nil }
var links: [Link]?
if let linksDictionary = dictionary["links"] as? [Any] {
links = linksDictionary.map { Link(jsonObject: $0)! }
}
self.init(id: id, links: links)
}
}
extension Link: Jsonable {
var jsonObject: Any { return [ "key1": key1, "key2": key2, "name": name, "link": link ] }
init?(jsonObject: Any) {
guard let dictionary = jsonObject as? [String: Any],
let key1 = dictionary["key1"] as? String,
let key2 = dictionary["key2"] as? String,
let name = dictionary["name"] as? String,
let link = dictionary["link"] as? String else {
return nil
}
self.init(key1: key1, key2: key2, name: name, link: link)
}
}
Then you can do stuff like:
let object = try JSONSerialization.jsonObject(with: data)
var foo = Foo(jsonObject: object)!
Or:
foo.links?.append(Link(key1: "j", key2: "k", name: "l", link: "m"))
let data = try! JSONSerialization.data(withJSONObject: foo.jsonObject)
This was the solution:
var arrLinks : Array<Dictionary<String, Any>> = []
for link in myArray {
var dict : Dictionary<String, Any> = [:]
dict["key1"] = link.name
dict["key2"] = link.ghostBefore
dict["key3"] = link.ghostAfter
arrLinks.append(dict)
}
myDict["links"] = arrLinks as AnyObject

Swift splitting "abc1.23.456.7890xyz" into "abc", "1", "23", "456", "7890" and "xyz"

In Swift on OS X I am trying to chop up the string "abc1.23.456.7890xyz" into these strings:
"abc"
"1"
"23"
"456"
"7890"
"xyz"
but when I run the following code I get the following:
=> "abc1.23.456.7890xyz"
(0,3) -> "abc"
(3,1) -> "1"
(12,4) -> "7890"
(16,3) -> "xyz"
which means that the application correctly found "abc", the first token "1", but then the next token found is "7890" (missing out "23" and "456") followed by "xyz".
Can anyone see how the code can be changed to find ALL of the strings (including "23" and "456")?
Many thanks in advance.
import Foundation
import XCTest
public
class StackOverflowTest: XCTestCase {
public
func testRegex() {
do {
let patternString = "([^0-9]*)([0-9]+)(?:\\.([0-9]+))*([^0-9]*)"
let regex = try NSRegularExpression(pattern: patternString, options: [])
let string = "abc1.23.456.7890xyz"
print("=> \"\(string)\"")
let range = NSMakeRange(0, string.characters.count)
regex.enumerateMatchesInString(string, options: [], range: range) {
(textCheckingResult, _, _) in
if let textCheckingResult = textCheckingResult {
for nsRangeIndex in 1 ..< textCheckingResult.numberOfRanges {
let nsRange = textCheckingResult.rangeAtIndex(nsRangeIndex)
let location = nsRange.location
if location < Int.max {
let startIndex = string.startIndex.advancedBy(location)
let endIndex = startIndex.advancedBy(nsRange.length)
let value = string[startIndex ..< endIndex]
print("\(nsRange) -> \"\(value)\"")
}
}
}
}
} catch {
}
}
}
It's all about your regex pattern. You want to find a series of contiguous letters or digits. Try this pattern instead:
let patternString = "([a-zA-Z]+|\\d+)"
alternative 'Swifty' way
let str = "abc1.23.456.7890xyz"
let chars = str.characters.map{ $0 }
enum CharType {
case Number
case Alpha
init(c: Character) {
self = .Alpha
if isNumber(c) {
self = .Number
}
}
func isNumber(c: Character)->Bool {
return "1234567890".characters.map{ $0 }.contains(c)
}
}
var tmp = ""
tmp.append(chars[0])
var type = CharType(c: chars[0])
for i in 1..<chars.count {
let c = CharType(c: chars[i])
if c != type {
tmp.append(Character("."))
}
tmp.append(chars[i])
type = c
}
tmp.characters.split(".", maxSplit: Int.max, allowEmptySlices: false).map(String.init)
// ["abc", "1", "23", "456", "7890", "xyz"]