I'm trying to model a scenario where an initial request will provide several different lists of Ids, then a later request needs to submit the values in these lists (if they exist) as part of it's JSON payload. None of the lists is guaranteed to be in the response, and if it isn't, then there is no corresponding entry at all for it in the later JSON.
For example, call 1 might return 3 different lists of Ids that can be saved for later calls...
.check(
jsonPath("$..manager").findAll.optional.saveAs("managemerIds"),
jsonPath("$..employee").findAll.optional.saveAs("employeeIds"),
jsonPath("$..temp").findAll.optional.saveAs("tempIds")
)
Later I need to submit these as a request body formatted like
{"managers":"${managerIds.jsonStringify()}",
"employees":"${employeeIds.jsonStringify()}",
"temps":"${tempIds.jsonStringify()}"}
but if one of the lists of Ids is empty, then it can't be submitted in the JSON at all - ie: if there were no tempId's from the first request, then the JSON payload needs to look like
{"managers":"${managerIds.jsonStringify()}",
"employees":"${employeeIds.jsonStringify()}"}
I could get this working by using a transformOption on the check to set the session variable to an empty list and then have conditional building of the JSON payload by doing something like
jsonPath("$..temp").findAll.transformOption(ids => ids.orElse(Some(Seq.empty[String])).success).saveAs("tempIds"))
but I was hoping for something more scala idiomatic using Options.
I can generate the required JSON for the body this way with something like
private def createPayload(
managers: Option[String] = None,
employees: Option[String] = None,
temps: Option[String] = None) : String = {
Map("managers" -> managers,
"employees" -> employees,
"temps" -> temps
).foldLeft(JsObject.empty){ case (result, (key, values)) => {
values match {
case Some(ids) => result ++ result.transform((__ \ key).json.put(JsString(ids))).get
case None => result
}
}}.toString()
but I can't work out a way to just pass the option that results from attempting to resolve the session variable
so I've come up with a solution that seems to work pretty well - I just had to resolve some session variables in the StringBody function and build the JSON according to requirements.
using the play framework's JSON capabilities is probably overkill though
def contactPeople(managers: Expression[Seq[String]], employees: Expression[Seq[String]], temps: Expression[Seq[String]]: ChainBuilder = {
exec(http(...)
.post(...)
.headers(...)
.body(StringBody(session =>
Map("managers" -> managers, "employees" -> employees, "temps" -> temps)
.foldLeft(JsObject.empty){ case (result, (key, values)) => {
values.apply(session) match {
case Success(ids) => result ++ result.transform((__ \ key).json.put(Json.toJson[Seq[String]](ids))).get
case Failure(error) => result
}
}}.toString
))
...
}
Related
I'm bumbling my way through adding a back-end to my site and have decided to get acquainted with graphQL. I may be structuring things totally the wrong way, however from following some tutorials I have a React front-end (hosted on Vercel), so I have created an api folder in my app to make use of Vercel's serverless functions. I'm using Apollo server and I decided to go with Fauna as my database.
I've successfully been able to return an entire collection via my API. Now I wish to be able to return the collection sorted by my id field.
To do this I created an index which looks like this:
{
name: "sort_by_id",
unique: false,
serialized: true,
source: "my_first_collection",
values: [
{
field: ["data", "id"]
},
{
field: ["ref"]
}
]
}
I then was able to call this via my api and get back and array, which simply contained the ID + ref, rather than the associated documents. I also could only console log it, I assume because the resolver was expecting to be passed an array of objects with the same fields as my typedefs. I understand I need to use the ref in order to look up the documents, and here is where I'm stuck. An index record looks as follows:
[1, Ref(Collection("my_first_collection"), "352434683448919125")]
In my resolvers.js script, I am attempting to receive the documents of my sorted index list. I've tried this:
async users() {
const response = await client.query(
q.Map(
q.Paginate(
q.Match(
q.Index('sort_by_id')
)
),
q.Lambda((ref) => q.Get(ref))
)
)
const res = response.data.map(item => item.data);
return [... res]
}
I'm unsure if the problem is with how I've structured my index, or if it is with my code, I'd appreciate any advice.
It looks like you also asked this question on the Fauna discourse forums and got an answer there: https://forums.fauna.com/t/unable-to-return-a-list-of-documents-via-an-index/3511/2
Your index returns a tuple (just an array in Javascript) of the data.id field and the ref. You confirmed that with your example result
[
/* data.id */ 1,
/* ref */ Ref(Collection("my_first_collection"), "352434683448919125")
]
When you map over those results, you need to Get the Ref. Your query uses q.Lambda((ref) => q.Get(ref)) which passes the whole tuple to Get
Instead, use:
q.Lambda(["id", "ref"], q.Get(q.Var("ref")))
// or with JS arrow function
q.Lambda((id, ref) => q.Get(ref))
or this will work, too
q.Lambda("index_entry", q.Get(q.Select(1, q.Var("index_entry"))))
// or with JS arrow function
q.Lambda((index_entry) => q.Get(q.Select(1, index_entry)))
The point is, only pass the Ref to the Get function.
My response looks like this
{
"data":[
{
"id":"919274ee42fe01d40f89f51239009a2b",
"descriptor":"Long Count_copy_1938",
"alias":"longCount_Copy_1938",
"type":"Numeric"
},
{
"id":"435274ee42fe01d40f89f51239009a2b",
"descriptor":"Long Count2",
"alias":"longCount_Copy2",
"type":"Numeric"
},
{
"id":"345274ee42fe01d40f89f51239009a2b",
"descriptor":"Short Count2",
"alias":"Short count",
"type":"Numeric"
}
]
}
I would like to extract "descriptor":"id" to a Map. After mapping, the Map object should look like
"Long Count_copy_1938" -> "919274ee42fe01d40f89f51239009a2b"
"Long Count2" -> "435274ee42fe01d40f89f51239009a2b"
"Short Count2" -> "345274ee42fe01d40f89f51239009a2b"
Here is how I am achieving it, Let me know if there is a better way. Thanks!
exec(http("Get Field ids")
.get(s"${wqlDataSources}/")
.check(status.is(200),
jsonPath("$.data[*].descriptor").findAll.saveAs("descriptors"),
jsonPath("$.data[*].id").findAll.saveAs("ids")))
.exec(session => {
val descriptors = session("descriptors").as[Vector[String]]
val ids = session("ids").as[Vector[String]]
val fieldIdMap = (descriptors zip ids).toMap
session.set("fieldIdResultMap", fieldIdMap)
session.remove("descriptors").remove("ids")
})
Most JSONPath implementations support to extract multiple values in one go using the [,] union operator, e.g. $..['id','descriptor'] to matches your two properties.
(However, the availability and the result of the union is neither universal nor consistent, if you check the above path online here by switching to Goessner or Jayway you will notice the result are not the same, the Gattling tab on the test site even throws an error; I cannot tell if it would work if the site would use the latest version.)
I could not find any documentation that confirmed Gatling supports unions but I found this test on Gatling's Github repo that has a union: JsonPath.query("$.menu['file','year']", json) ... So, unioning should work, in general.
With some trial and error, I found this path that works using Gatling (even with older versions):
$.data[*]['id','descriptor']
Which returns:
[
"919274ee42fe01d40f89f51239009a2b",
"Long Count_copy_1938",
"435274ee42fe01d40f89f51239009a2b",
"Long Count2",
"345274ee42fe01d40f89f51239009a2b",
"Short Count2"
]
So, you should be able to map the key/value pairs in one go using this path!
Here is 1 more solution that worked,
exec(http("Get Field ids for the data source")
.get(s"${wqlDataSources}/" + "${datasourceId}/fields)
.check(status.is(200),
jsonPath("$.data[*].descriptor").findAll.saveAs("descriptors"),
jsonPath("$.data[*].id").findAll.saveAs("ids")))
.exec(session => {
val descriptors = session("descriptors").as[Vector[String]]
val ids = session("ids").as[Vector[String]]
val currentFieldIdMap = (descriptors zip ids).toMap
})
I have these values stored in a .json as a very basic xp system (im aware of corruption issues, like to learn json before moving to db)
"267752827723492576":{"xp":308,"level":1}, "267752827723492576":{"xp":308,"level":1}
i want to import userid, xp, and level into a variable so i can make a leaderboard command, i already have working code for doing the comparison and sorting (below) . "user" being the variable containing my data from the json file
var users = {
"":{"xp":0,"level":0},
"":{"xp":0,"level":0},
"":{"xp":0,"level":0},
"":{"xp":0,"level":0},
"":{"xp":0,"level":0},
};
let board = Object.entries(users)
.map(([key, val]) => ({id: key, ...val}))
.sort((a, b) => b.xp- a.xp);
console.log(board);
Post is not clear, but you just wan't to import json?
const users = require("path_to.json")
You can also use fs.readFile alongside JSON.parse
Also instead of using an object to store the data you can use an array
with this format: (same format after you used your map method)
[{ id: "", xp: 200, level: 2}]
For preformance both have ups and downs, for actually getting a user by id an object is probably faster, but since you have to use Object.entries and map it probably evens out
Incase you do switch to array, here's how you would fetch a user
// ... just stands for data here, not the deconstructing or rest syntax
const json = [{...}, {...}];
const user = json.find(f => f.id === "the-id-here");
I'am try to use single .check(regex to extract multiple values. Code below represent extracting 3 groups.
val goToProduct = http("""GoTo_${product}""")
.get("""${product}""")
.headers(headers_0)
.check(regex("""name="([\s\S]+?)" value="(.+)" id="(.+)"""").ofType[(String,String,String)].saveAs("description")
After this I'am trying to use extracted values separately (e.g. description._1 as Tuple3, or description(1) as Collection). But it's always fails.
This works, but maybe there is more convenient way to do this (like val._1)
session("description").validate[(String, String, String)].map { case
(prod_name, prod_value, prod_id) =>
session.setAll("prod_name" -> prod_name, "prod_value" -> prod_value,
"prod_id" -> prod_id)
Trying this
.exec { session =>
println(session("${description._1}").as[String])
session }
Will give an error: 'hook-1' crashed with 'j.u.NoSuchElementException: No attribute named '${description._1}' is defined', forwarding to the next one
This line
println(session("description").as[String])
Shows Tuple3: (addtocart_37.EnteredQuantity,1,/addproducttocart/details/37/1)
the gatling EL supports tuples so you can use calls like
"${description._1}"
to access the product, for example
To get the value in order to use it somewhere other than in a dsl call that takes an Expression, you can just retrieve it in a session action (where you can't use EL)
exec(session => {
println(session("description").as[(String, String, String)]._1)
session
})
Realmswift Data Model
class User {
let id = RealmOptional<Int>()
dynamic var name = ""
let albums = List<Album>()
override static func primaryKey() -> String {
return "id"
}
}
class Album: Object {
dynamic albumName = ""
let imageIDs = List<ImageID>()
}
class ImageID: Object {
let imageId = RealmOptional<Int>()
}
JSON data
{
"10001": {
"id" : 10001,
"name": "John",
"album": {
"albums": [
{
"albumName": "Summer1999",
"imageIds": [11223, 11224, 11227]
},
{
"albumName": "Xmas1999",
"imageIds": [22991, 22997]
},
{
"albumName": "NewYear2000",
"imageIds": [5556, 776, 83224, 87543]
}
]
}
}
}
I have the above json data and I m using SwiftyJSON to parse the data then write into realm. Everything is working great except for checking and updating of data (for example imageIds on json file have changed).
Question: How do i compare the JSON arrays and RealmSwift List to determine it any updates need to be written into the database?
You can take advantage of your primary key here. As Realm Swift documentation states:
Creating and Updating Objects With Primary Keys: If your model class
includes a primary key, you can have Realm intelligently update or add
objects based off of their primary key values using
Realm().add(_:update:).
So (I assume that you get the JSON from some kind of a request (REST etc.) and then parse it with SwiftyJSON to create a 'User' object) you can treat the new 'User' object as regular new 'User' and try to add it to Realm as usual, but 'update' parameter must be 'true'. If there already was a user with the id of the 'User' object you are trying to add, it will just update the existing 'User' i.e. changing its modified values from the new 'User' created by parsing new JSON data. This might look something like this:
//Parse JSON and create a 'User'
let newUserFromJSON = parseAndCreateUserFromJSON(JSONData)
let realm = try! Realm()
do {
try realm.write {
realm.add(newUserFromJSON, update: true)
}
} catch let error as NSError {
print("error writing to realm: \(error.description) & \(error)")
} catch {
print("error writing to realm: UNKNOWN ERROR")
}
I'm afraid there's probably no easy answer to this. There's no mechanism in Realm to compare the contents of a Realm Object to an external object to see if their data matches. You would need to iterate through each object in the Realm object and manually compare it.
This wouldn't be too much code to write (Since you can get a list of all of the Realm file's properties via the objectSchema property of Realm objects, and then use key-value coding to pull them out in a single for loop), but would still be a fair amount of overhead to perform the compare.
That being said, if what you're wanting to look at is just certain properties that might change (i.e. like you said, just the imageIDs property), then you could easily just check the values you need.
What bcamur has suggested is definitely the quickest (And usually preferred for JSON handing) solution here. As long as you've set your primary key properly, you can call Realm.add(_:, update:) with update set to true to update the object.
Please keep in mind this doesn't merge the new data with what was already in Realm; it'll completely overwrite the old object with the new values, which if it sounds like your ID numbers are changing, would be the best course of action.