JSONata: flattening recursion that tracks path of recursion - recursion

I have a nested, recursive JSON of arbitrary depth where a node can either be a parent of more nodes (i.e. it has a structure array containing nodes, which is the case for schema or fieldGroup nodes) or it can be a leaf (field node).
I try to use JSONata to map it to a flattened structure. I have difficulty to track the recursion path - in this case I need to build a path of ids inside each recursively mapped field, but I can't.
Source JSON with three levels of nesting ("House no." is level 3):
{
"schema": {
"id": "S05000058",
"label": "Request to breed mice",
"structure": [
{
"contains": {
"field": {
"id": "F05001343",
"label": "Accept terms and conditions"
}
}
},
{
"contains": {
"field": {
"id": "F05001344",
"label": "Agree to share registration with ministry of mice"
}
}
},
{
"contains": {
"fieldGroup": {
"id": "G05000496",
"label": "Requesting person",
"structure": [
{
"contains": {
"field": {
"id": "F05000059",
"label": "Last name"
}
}
},
{
"contains": {
"field": {
"id": "F05000060",
"label": "First name"
}
}
},
{
"contains": {
"fieldGroup": {
"id": "G05000428",
"label": "Street address",
"structure": [
{
"contains": {
"field": {
"id": "F00000053",
"label": "Street"
}
}
},
{
"contains": {
"fieldGroup": {
"id": "G05000429",
"label": "House no",
"structure": [
{
"contains": {
"field": {
"id": "F00000016",
"label": "Number"
}
}
},
{
"contains": {
"field": {
"id": "F00000016",
"label": "Appendix"
}
}
}
]
}
}
}
]
}
}
},
{
"contains": {
"fieldGroup": {
"id": "G00000113",
"label": "Location",
"structure": [
{
"contains": {
"field": {
"id": "F00000054",
"label": "Postal code"
}
}
},
{
"contains": {
"field": {
"id": "F00000035",
"label": "City"
}
}
}
]
}
}
},
{
"contains": {
"field": {
"id": "F05000523",
"label": "Country"
}
}
}
]
}
}
}
]
}
}
My JSONata mapping uses the descendants path operator ** to flatten fieldGroups recursively, starting from the second nesting level.
https://try.jsonata.org/lfQdZcAe-
My problem is that I do not know how to get access to the ids of the parent nodes during ** recursion, therefore my $mapFields() function is unable to build an idPath for the recursion (last $mapFields() invocation below):
(
$mapFields := function($datenfelder, $prefix) {
$datenfelder.{
"fields": [
{
"label": label,
"idPath": $prefix & '.' & id
}
]
}
};
{
"idPath": schema.id,
"sections": $append(
/* top-level fields go into general section */
{
"title": "Top level fields",
"fieldGroups": [
{
"title": "",
"rows": $mapFields(schema.structure.contains.field, schema.id)
}
]
},
/* subsequent sections contain top-level field groups */
schema.structure.contains.fieldGroup.{
"title": label,
"idPath": $$.schema.id & '.' & id,
"fieldGroups": [
$append(
{
"title": "",
"rows": $mapFields(structure.contains.field, $$.schema.id & '.' & id)
},
/* create fieldGroups recursively */
structure.contains.**.fieldGroup.{
"title": label,
/* how to build group idPath from third level: */
"idPath": $$.schema.id & '.' & id,
/* how to pass recursive path prefix here: */
"rows": $mapFields($.structure.contains.field, ???)
}
)
]
}
)
})
My JSONata fails to produce complete idPaths starting from the fieldGroup "Street address" (which is on the second level):
{
"idPath": "S05000058",
"sections": [
{
"title": "Top level fields",
"fieldGroups": [
{
"title": "",
"rows": [
{
"fields": [
{
"label": "Accept terms and conditions",
"idPath": "S05000058.F05001343"
}
]
},
{
"fields": [
{
"label": "Agree to share registration with ministry of mice",
"idPath": "S05000058.F05001344"
}
]
}
]
}
]
},
{
"title": "Requesting person",
"idPath": "S05000058.G05000496",
"fieldGroups": [
{
"title": "",
"rows": [
{
"fields": [
{
"label": "Last name",
"idPath": "S05000058.G05000496.F05000059"
}
]
},
{
"fields": [
{
"label": "First name",
"idPath": "S05000058.G05000496.F05000060"
}
]
},
{
"fields": [
{
"label": "Country",
"idPath": "S05000058.G05000496.F05000523"
}
]
}
]
},
{
"title": "Street address",
"idPath": "S05000058.G05000428",
"rows": {
"fields": [
{
"label": "Street",
"idPath": ".F00000053" /* incomplete */
}
]
}
},
{
"title": "House no",
/* incomplete, should be "S05000058.G05000428.G05000429": */
"idPath": "S05000058.G05000429",
"rows": [
{
"fields": [
{
"label": "Number",
"idPath": ".F00000016" /* incomplete */
}
]
},
{
"fields": [
{
"label": "Appendix",
"idPath": ".F00000016" /* incomplete */
}
]
}
]
},
{
"title": "Location",
"idPath": "S05000058.G00000113",
"rows": [
{
"fields": [
{
"label": "Postal code",
"idPath": ".F00000054" /* incomplete */
}
]
},
{
"fields": [
{
"label": "City",
"idPath": ".F00000035" /* incomplete */
}
]
}
]
}
]
}
]
}
Note that it is not sufficient to pass $$.schema.id & '.' & id to the recursive $mapFields() call, as shown by the "House No" fieldGroup on the third level - all fields starting from the third level would get a fixed prefix which does not reflect the actual depth of recursion.
I have also tried to create a recursion manually as in How to flatten nested object to single depth object with JSONata?, but I didn't manage to iterate over the structure array during recursion.

I believe the "parent" path operator is what you want here: https://stedi.link/NR2Ax3Z
You can check how the parent operator works in the docs: https://docs.jsonata.org/path-operators#-parent

Related

Google Chat API: How do I switch between Cards v1 and Cards v2?

According to the docs, Cards v1 is deprecated, and Cards v2 seems to be much more feature-rich.
But when I use the Cards v2 syntax, the API complains about unknown or missing fields. How can I tell the Google servers that I want to use the v2 API? The documentation does not seem to tell anything about that.
All tutorials that I can find seem to use Cards v1 only. I'm mostly following the official tutorial in Python.
I tried to send message with card v2 with this syntax:
"cards_v2": [[{
"card_id": "addContact",
"card": {}
}]
Request:
POST https://chat.googleapis.com/v1/spaces/space_name/messages
{
"cards_v2": [{
"card_id": "addContact",
"card": {
"header": {
"title": "Rolodex",
"subtitle": "Manage your contacts!",
"imageUrl": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
"imageType": "CIRCLE"
},
"sections": [
{
"widgets": [
{
"buttonList": {
"buttons": [
{
"text": "Add Contact",
"onClick": {
"action": {
"function": "openDialog",
"interaction": "OPEN_DIALOG"
}
}
}
]
},
"horizontalAlignment": "CENTER"
}
]
}
]
}
}]
,
"thread": {
"name": "spaces/space_name/threads/thread_name"
}
}
Taken from here:
https://developers.google.com/chat/how-tos/dialogs?hl=en
Use the following JSON schema to use v2 cards;
{
"cardsV2": [
{
"cardId": "unique-card-id",
"card": {
"header": {
"title": "Sasha",
"subtitle": "Software Engineer",
"imageUrl":
"https://developers.google.com/chat/images/quickstart-app-avatar.png",
"imageType": "CIRCLE",
"imageAltText": "Avatar for Sasha",
},
"sections": [
{
"header": "Contact Info",
"collapsible": true,
"uncollapsibleWidgetsCount": 1,
"widgets": [
{
"decoratedText": {
"startIcon": {
"knownIcon": "EMAIL",
},
"text": "sasha#example.com",
}
},
{
"decoratedText": {
"startIcon": {
"knownIcon": "PERSON",
},
"text": "<font color=\"#80e27e\">Online</font>",
},
},
{
"decoratedText": {
"startIcon": {
"knownIcon": "PHONE",
},
"text": "+1 (555) 555-1234",
}
},
{
"buttonList": {
"buttons": [
{
"text": "Share",
"onClick": {
"openLink": {
"url": "https://example.com/share",
}
}
},
{
"text": "Edit",
"onClick": {
"action": {
"function": "goToView",
"parameters": [
{
"key": "viewType",
"value": "EDIT",
}
],
}
}
},
],
}
},
],
},
],
},
}
],
}
visit Google chat docs for more

Is it possible to get the list of pages and devices in a single GA4 v1beta request?

I'm having to make 2 runRealtimeReport requests, one to get the device category and another to get the list of the most accessed pages.
What I would like to get is the general amount of hits to the site via mobile, desktop and tablet, but including the two dimensions, return the pages and the device category of each access.
If I include the deviceCategory and unifiedScreenName dimensions in the same request, the data is grouped as shown in the example below:
Request body:
{
"metrics": [
{
"name": "activeUsers"
}
],
"dimensions": [
{
"name": "deviceCategory"
},
{
"name": "unifiedScreenName"
}
],
"limit": 2
}
Response:
{
"dimensionHeaders": [
{
"name": "deviceCategory"
},
{
"name": "unifiedScreenName"
}
],
"metricHeaders": [
{
"name": "activeUsers",
"type": "TYPE_INTEGER"
}
],
"rows": [
{
"dimensionValues": [
{
"value": "mobile"
},
{
"value": "Title page 01"
}
],
"metricValues": [
{
"value": "9"
}
]
},
{
"dimensionValues": [
{
"value": "mobile"
},
{
"value": "Title Page 2"
}
],
"metricValues": [
{
"value": "3"
}
]
}
],
"rowCount": 16,
"kind": "analyticsData#runRealtimeReport"
}

Can I get the user email within a Velocity template of AWS Amplify?

When I query a resolver in my GraphQL API, in which I have added a $util.error($ctx) to return the context object, I get the following result (removed unnecessary values).
{
"data": {
"listXData": null
},
"errors": [
{
"message": {
"arguments": {},
"args": {},
"info": {
"fieldName": "listXData",
"variables": {},
"parentTypeName": "Query",
"selectionSetList": [
"items",
"items/id",
"items/createdAt",
"items/updatedAt",
"nextToken"
],
"selectionSetGraphQL": "{\n items {\n id\n createdAt\n updatedAt\n }\n nextToken\n}"
},
"request": {...},
"identity": {
"sub": "",
"issuer": "",
"username": "013fe9d2-95f7-4885-83ec-b7e2e0a1423f",
"sourceIp": "",
"claims": {
"origin_jti": "",
"sub": "",
"event_id": "",
"token_use": "",
"scope": "",
"auth_time": ,
"iss": "",
"exp": ,
"iat": ,
"jti": "",
"client_id": "",
"username": "013fe9d2-95f7-4885-83ec-b7e2e0a1423f"
},
"defaultAuthStrategy": "ALLOW"
},
"stash": {},
"source": null,
"result": {
"items": [],
"scannedCount": 0,
"nextToken": null
},
"error": null,
"prev": {
"result": {}
}
},
"errorType": null,
"data": null,
"errorInfo": null,
"path": [
"listXData"
],
"locations": [
{
"line": 2,
"column": 3,
"sourceName": "GraphQL request"
}
]
}
]
}
As you can see, the username is an ID, however I would prefer to (also) have the email. Is it possible to get the user email (within the Velocity template)?
Let me know if I need to add more details or if my question is unclear.
The identity context only returns back the Cognito username for the user pool. You will need to setup pipeline functions to perform additional queries to get your user information. Here is one intro to setting them up.
At this point, it seems that it is not possible to do this purely by vtl.
I have implemented it using a lambda function, as follow:
Lambda function (node):
/* Amplify Params - DO NOT EDIT
ENV
REGION
Amplify Params - DO NOT EDIT */
const aws = require('aws-sdk')
const cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({
apiVersion: '2016-04-18',
region: 'eu-west-1'
})
exports.handler = async (context, event, callback) => {
if (!context.identity?.username) {
callback('Not signed in')
}
const params = {
'AccessToken': context.request.headers.authorization
}
const result = await cognitoidentityserviceprovider.getUser(params).promise()
const email = result.UserAttributes.find(attribute => attribute.Name === 'email')
callback(null, JSON.stringify({ email }))
}
CustomResources.json
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "An auto-generated nested stack.",
"Metadata": {...},
"Parameters": {...},
"Resources": {
"GetEmailLambdaDataSourceRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Join": [
"-",
[
"GetEmail17ec",
{
"Ref": "GetAttGraphQLAPIApiId"
},
{
"Ref": "env"
}
]
]
},
{
"Fn::Join": [
"-",
[
"GetEmail17ec",
{
"Ref": "GetAttGraphQLAPIApiId"
}
]
]
}
]
},
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "appsync.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Policies": [
{
"PolicyName": "InvokeLambdaFunction",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Sub": [
"arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:GetEmail-${env}",
{
"env": {
"Ref": "env"
}
}
]
},
{
"Fn::Sub": [
"arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:GetEmail",
{}
]
}
]
}
}
]
}
}
]
}
},
"GetEmailLambdaDataSource": {
"Type": "AWS::AppSync::DataSource",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"Name": "GetEmailLambdaDataSource",
"Type": "AWS_LAMBDA",
"ServiceRoleArn": {
"Fn::GetAtt": [
"GetEmailLambdaDataSourceRole",
"Arn"
]
},
"LambdaConfig": {
"LambdaFunctionArn": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Sub": [
"arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:GetEmail-${env}",
{
"env": {
"Ref": "env"
}
}
]
},
{
"Fn::Sub": [
"arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:GetEmail",
{}
]
}
]
}
}
},
"DependsOn": "GetEmailLambdaDataSourceRole"
},
"InvokeGetEmailLambdaDataSource": {
"Type": "AWS::AppSync::FunctionConfiguration",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"Name": "InvokeGetEmailLambdaDataSource",
"DataSourceName": "GetEmailLambdaDataSource",
"FunctionVersion": "2018-05-29",
"RequestMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/${ResolverFileName}",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
},
"ResolverFileName": {
"Fn::Join": [
".",
[
"InvokeGetEmailLambdaDataSource",
"req",
"vtl"
]
]
}
}
]
},
"ResponseMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/${ResolverFileName}",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
},
"ResolverFileName": {
"Fn::Join": [
".",
[
"InvokeGetEmailLambdaDataSource",
"res",
"vtl"
]
]
}
}
]
}
},
"DependsOn": "GetEmailLambdaDataSource"
},
"IsOrganizationMember": {
"Type": "AWS::AppSync::FunctionConfiguration",
"Properties": {
"FunctionVersion": "2018-05-29",
"ApiId": {
"Ref": "AppSyncApiId"
},
"Name": "IsOrganizationMember",
"DataSourceName": "PermissionsPerOrganizationTable",
"RequestMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.isOrganizationMember.req.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
},
"ResponseMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.isOrganizationMember.res.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
}
}
},
"OrganizationAccessPipeline": {
"Type": "AWS::AppSync::Resolver",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"TypeName": "Query",
"Kind": "PIPELINE",
"FieldName": "listXData",
"PipelineConfig": {
"Functions": [
{
"Fn::GetAtt": [
"InvokeGetEmailLambdaDataSource",
"FunctionId"
]
},
{
"Fn::GetAtt": [
"IsOrganizationMember",
"FunctionId"
]
}
]
},
"RequestMappingTemplate": "{}",
"ResponseMappingTemplate": "$util.toJson($ctx.result)"
}
}
},
"Conditions": {...},
"Outputs": {...}
}
The lambda is created with the CLI and IsOrganizationMember is a regular VTL which has the user email in the $context.prev.result.

SABRE Hotel Book request is missing TravelItineraryRead in the response

I'm having an issue performing the following SABRe hotel book in the CRT environment using an orchestrated workflow.
{
"CreatePassengerNameRecordRQ": {
"version": "2.3.0",
"TravelItineraryAddInfo": {
"CustomerInfo": {
"ContactNumbers": {
"ContactNumber": [
{
"NameNumber": "1.1",
"Phone": "17805555555",
"PhoneUseType": "H"
}
]
},
"Email": [
{
"Address": "chris#test.com",
"Type": "TO"
}
],
"PersonName": [
{
"NameNumber": "1.1",
"PassengerType": "ADT",
"GivenName": "Chris",
"Surname": "test"
}
]
}
},
"HotelBook": {
"BookingInfo": {
"BookingKey": "5d07cdba-0123-4510-9f9a-5257973b5f98",
"RequestorID": "SG000000"
},
"Rooms": {
"Room": [
{
"Guests": {
"Guest": [
{
...
}
]
},
"RoomIndex": 1
}
]
},
"PaymentInformation": {
"FormOfPayment": {
"PaymentCard": {
"PaymentType": "CC",
"CardCode": "VI",
"CardNumber": "4111111111111111",
"ExpiryMonth": 3,
"ExpiryYear": "2024",
"FullCardHolderName": {
"FirstName": "Chris",
"LastName": "test",
"Email": "chris#test.com"
},
"CSC": "013",
"Address": {
...
},
"Phone": {
"PhoneNumber": "17805555555"
}
}
},
"Type": "GUARANTEE"
},
"POS": {
"Source": {
"RequestorID": {
"Type": 5,
"Id": "XXX",
"IdContext": "IATA"
},
"AgencyAddress": {
"AddressLine1": "1 Lincoln Blvd",
"CountryName": {
"Code": "US"
}
},
"AgencyName": "Flying Wings",
"ISOCountryCode": "US",
"PseudoCityCode": "1MNJ"
}
}
},
"PostProcessing": {
"EndTransaction": {
"Source": {
"ReceivedFrom": "Flying Wings Web"
},
"Email": {
"Ind": true
}
}
}
}
}
The response I get is:
{
"CreatePassengerNameRecordRS": {
"ApplicationResults": {
"status": "Complete",
"Success": [
{
"timeStamp": "2021-03-08T01:18:50.544-06:00"
}
]
},
"ItineraryRef": {
"ID": "VKIJSI"
}
},
"Links": [
]
}
So a successful booking however I am expecting a TravelItineraryRead returned in the response and am not getting one. Am I missing something in the request?
Thanks.
try redisplaing the newly created reservation.
"RedisplayReservation": {
"waitInterval": 100
}
that should include the itinerary in the response

How Can I group by Gremlin Server (Titan 1.0) Response on Basis of Vertex Id?

I'm trying following query :
g.V(835776).out('Follow').in('WallPost').order().by('PostedTimeLong', decr).range(0,2)
and I'm getting following response :
{
"requestId": "524462bc-5e46-40bf-aafd-64d00351dc87",
"status": {
"message": "",
"code": 200,
"attributes": { }
},
"result": {
"data": [
{
"id": 1745112,
"label": "Post",
"type": "vertex",
"properties": {
"PostImage": [
{
"id": "sd97-11ejc-2wat",
"value": ""
}
],
"PostedByUser": [
{
"id": "sc2j-11ejc-2txh",
"value": "orbitpage#gmail.com"
}
],
"PostedTime": [
{
"id": "scgr-11ejc-2upx",
"value": "2016-06-19T09:17:27.6791521Z"
}
],
"PostMessage": [
{
"id": "sbob-11ejc-2t51",
"value": "Hello #[tag:Urnotice_Profile|835776|1] , #[tag:Abhinav_Srivastava|872488|1] and #[tag:Rituraj_Rathore|839840|1]"
}
],
"PostedTimeLong": [
{
"id": "scuz-11ejc-2vid",
"value": 636019246476802029
}
]
}
},
{
"id": 1745112,
"label": "Post",
"type": "vertex",
"properties": {
"PostImage": [
{
"id": "sd97-11ejc-2wat",
"value": ""
}
],
"PostedByUser": [
{
"id": "sc2j-11ejc-2txh",
"value": "orbitpage#gmail.com"
}
],
"PostedTime": [
{
"id": "scgr-11ejc-2upx",
"value": "2016-06-19T09:17:27.6791521Z"
}
],
"PostMessage": [
{
"id": "sbob-11ejc-2t51",
"value": "Hello #[tag:Urnotice_Profile|835776|1] , #[tag:Abhinav_Srivastava|872488|1] and #[tag:Rituraj_Rathore|839840|1]"
}
],
"PostedTimeLong": [
{
"id": "scuz-11ejc-2vid",
"value": 636019246476802029
}
]
}
}
],
"meta": { }
}
}
since same post is posted on two different Id's it is coming twice in response. I want to group by response on basis of vertex id ( both have same vertex id. or i just want to get one object out of them as both are same only.
I've tried following queries but nothing worked for me :
g.V(835776).out('Follow').in('WallPost').groupBy{it.id}.order().by('PostedTimeLong', decr).range(0,3)
g.V(835776).out('Follow').in('WallPost').group().by(id).order().by('PostedTimeLong', decr).range(0,3)
How can I group by the result on basis of vertex id.
The query
g.V(835776).out('Follow').in('WallPost').group().by(id).order().by('PostedTimeLong', decr).range(0,3)
should work, although order().by() and range() will have no effect. However, I don'tthink you really want to group(), you more likely want to dedup():
g.V(835776).out('Follow').in('WallPost').dedup().order().by('PostedTimeLong', decr).limit(3)

Resources