Cypher request to Generate a nested recursive json - neo4j

So far I have created the following nodes:
CREATE
(b:Brand {identifier: 'ANTIINFECTIEUX',brand:'ANTIINFECTIEUX',description :'ANTIINFECTIEUX' }),
(b)<-[:IS_A_CATALOG_BELONGING_TO]-(c:Catalog {identifier: 'ANTIBACTERIENS',catalogName:'ANTIBACTERIENS',brand:'ANTIINFECTIEUX',brandIdentifier:'ANTIINFECTIEUX',description :'ANTIBACTERIENS'}),
(c)<-[:IS_A_PRODUCT_BELONGING_TO_THAT_CATALOG]-(p:Product {identifier: 'Amikacine',productName:'Amikacine',brand:'ANTIINFECTIEUX',brandIdentifier:'ANTIINFECTIEUX',catalog:'ANTIBACTERIENS',catalogIdentifier:'ANTIBACTERIENS',description :'Amikacine',DCI:'Amikacine', Dosage:'1g', Forme:'Inj', Case:'false', Poste:'false', CTRE:'false', CHR:'true', CHN:'true', Reference:'Amiklin', price:'200.75', stock:'500',NumeroDeLot:'87',CodeBarre:'87878787878787878',QuantiteDemandee:'50'}),
(p)-[:IS_A_PRODUCT_BELONGING_TO_THAT_BRAND]->(b),
(c)<-[:IS_A_PRODUCT_BELONGING_TO_THAT_CATALOG]-(p2:Product {identifier: 'Amoxicilline',productName:'Amoxicilline',brand:'ANTIINFECTIEUX',brandIdentifier:'ANTIINFECTIEUX',catalog:'ANTIBACTERIENS',catalogIdentifier:'ANTIBACTERIENS',description :'Amoxicilline',DCI:'Amoxicilline', Dosage:'500mg', Forme:'Gélules', Case:'false', Poste:'true', CTRE:'true', CHR:'true', CHN:'true', Reference:'Clamoxyl', price:'250.75', stock:'700',NumeroDeLot:'8777',CodeBarre:'998898979797',QuantiteDemandee:'50'}),
(p2)-[:IS_A_PRODUCT_BELONGING_TO_THAT_BRAND]->(b),
(b1:Brand {identifier: 'ANESTHESIQUES',brand:'ANESTHESIQUES',description :'ANESTHESIQUES' }),
(b1)<-[:IS_A_CATALOG_BELONGING_TO]-(c1:Catalog {identifier: 'Anesthesiques_Generaux_et_Gaz_medicaux',catalogName:'Anesthesiques_Generaux_et_Gaz_medicaux',brand:'ANESTHESIQUES',brandIdentifier:'ANESTHESIQUES',description :'Anesthésiques généraux et Gaz médicaux'}),
(c1)<-[:IS_A_PRODUCT_BELONGING_TO_THAT_CATALOG]-(p1:Product {identifier: 'Kétamine',productName:'Kétamine',brand:'ANESTHESIQUES',brandIdentifier:'ANESTHESIQUES',catalog:'Anesthesiques_Generaux_et_Gaz_medicaux',catalogIdentifier:'Anesthesiques_Generaux_et_Gaz_medicaux',description :'Kétamine',DCI:'Kétamine', Dosage:'50mg/amp', Forme:'Inj', Case:'false', Poste:'false', CTRE:'true', CHR:'true', CHN:'true', Reference:'Kétalar', price:'900.75', stock:'300',NumeroDeLot:'677',CodeBarre:'5454578788',QuantiteDemandee:'10'}),
(p1)-[:IS_A_PRODUCT_BELONGING_TO_THAT_BRAND]->(b1)
The purpose is to get a nested json like this:
{
"menuItems":[
{
"name":"Anesthesiques_Generaux_et_Gaz_medicaux",
"children":[
{
"name":"ANESTHESIQUES",
"children":[
{
"name":"Kétamine",
"ProductCHR":"true",
"ProductForme":"Inj",
"ProductCHN":"true",
"ProductReference":"Kétalar",
"ProductDCI":"Kétamine",
"ProductCase":"false",
"ProductDosage":"50mg/amp",
"ProductIdentifier":"Kétamine",
"ProductPoste":"false",
"ProductPrice":"900.75",
"ProductCTRE":"true",
"ProductStock":"300"
}
]
}
]
},
{
"name":"ANTIBACTERIENS",
"children":[
{
"name":"ANTIINFECTIEUX",
"children":[
{
"name":"Amikacine",
"ProductCHR":"true",
"ProductForme":"Inj",
"ProductCHN":"true",
"ProductReference":"Amiklin",
"ProductDCI":"Amikacine",
"ProductCase":"false",
"ProductDosage":"1g",
"ProductIdentifier":"Amikacine",
"ProductPoste":"false",
"ProductPrice":"200.75",
"ProductCTRE":"false",
"ProductStock":"500"
},
{
"name":"Amoxicilline",
"ProductCHR":"true",
"ProductForme":"Gélules",
"ProductCHN":"true",
"ProductReference":"Clamoxyl",
"ProductDCI":"Amoxicilline",
"ProductCase":"false",
"ProductDosage":"500mg",
"ProductIdentifier":"Amoxicilline",
"ProductPoste":"true",
"ProductPrice":"250.75",
"ProductCTRE":"true",
"ProductStock":"700"
}
]
}
]
}
]
}
To generate that JSON file I've made the following cypher request:
MATCH (Brand:Brand)
OPTIONAL MATCH (Brand)<-[:IS_A_CATALOG_BELONGING_TO]-(Catalog:Catalog)
OPTIONAL MATCH (Catalog)<-[:IS_A_PRODUCT_BELONGING_TO_THAT_CATALOG]-(Product:Product)
OPTIONAL MATCH (Product)-[:IS_A_PRODUCT_BELONGING_TO_THAT_BRAND]->(Brand)
WITH Brand, Catalog, Product
ORDER BY Product.identifier ASC
WITH Brand, Catalog,
collect({name: Product.DCI, ProductIdentifier:Product.identifier,ProductDCI:Product.DCI,
ProductDosage:Product.Dosage,ProductForme:Product.Forme,ProductCase:Product.Case,
ProductPoste:Product.Poste,ProductCTRE:Product.CTRE,ProductCHR:Product.CHR,ProductCHN:Product.CHN,
ProductReference:Product.Reference,ProductPrice:Product.price,ProductStock:Product.stock,
NumeroDeLot:Product.NumeroDeLot,CodeBarre:Product.CodeBarre,QuantiteDemandee:Product.QuantiteDemandee}) AS pNames
ORDER BY Catalog.identifier ASC
WITH Brand.identifier AS name,
collect(Catalog.identifier ) AS cname,
collect( pNames) AS children
ORDER BY name ASC
RETURN apoc.convert.toJson({name:name,cname:cname,children:children})
That request holds all infos need but
Unfortunately, the ideal output form it doesn't match my expectations (the resultant json that I really want) as you can see here.
"{
"children":[
[
{
"NumeroDeLot":"677",
"ProductReference":"Kétalar",
"ProductCase":"false",
"ProductPrice":"900.75",
"ProductCTRE":"true",
"QuantiteDemandee":"10",
"ProductCHR":"true",
"ProductForme":"Inj",
"ProductCHN":"true",
"ProductDCI":"Kétamine",
"ProductDosage":"50mg/amp",
"ProductIdentifier":"Kétamine",
"name":"Kétamine",
"ProductPoste":"false",
"CodeBarre":"5454578788",
"ProductStock":"300"}
]
],
"name":"ANESTHESIQUES",
"cname":["Anesthesiques_Generaux_et_Gaz_medicaux"]
}
"
"{
"children":[
[
{
"NumeroDeLot":"87",
"ProductReference":"Amiklin",
"ProductCase":"false",
"ProductPrice":"200.75",
"ProductCTRE":"false",
"QuantiteDemandee":"50",
"ProductCHR":"true",
"ProductForme":"Inj",
"ProductCHN":"true",
"ProductDCI":"Amikacine",
"ProductDosage":"1g",
"ProductIdentifier":"Amikacine",
"name":"Amikacine",
"ProductPoste":"false",
"CodeBarre":"87878787878787878",
"ProductStock":"500"
},
{
"NumeroDeLot":"8777",
"ProductReference":"Clamoxyl",
"ProductCase":"false",
"ProductPrice":"250.75",
"ProductCTRE":"true",
"QuantiteDemandee":"50",
"ProductCHR":"true",
"ProductForme":"Gélules",
"ProductCHN":"true",
"ProductDCI":"Amoxicilline",
"ProductDosage":"500mg",
"ProductIdentifier":"Amoxicilline",
"name":"Amoxicilline",
"ProductPoste":"true",
"CodeBarre":"998898979797",
"ProductStock":"700"
}
]
],
"name":"ANTIINFECTIEUX",
"cname":["ANTIBACTERIENS"]
}"
It would be great if someone could help me to achieve that purpose.
{
"menuItems":[
{
"name":"Brand Name1",
"children":[
{
"name":"Catalog Name",
"children":[
{
"name": "productName1",
.....................
etc
},
{
"name": "productName2"
.....................
},
........................
{
"name": "productNameN"
.....................
}
]
},
......................................
{
"name":"Catalog NameN",
"children":[
{
"name": "productName1ForCatalogNameN",
.....................
etc
},
{
"name": "productName2ForCatalogNameN"
.....................
},
........................
{
"name": "productNameNForCatalogNameN"
.....................
}
]
},
]
},
....................................................................
{
"name":"Brand NameN",
"children":[
{
"name":"Catalog Name",
"children":[
{
"name": "productName1",
.....................
etc
},
{
"name": "productName2"
.....................
},
........................
{
"name": "productNameN"
.....................
}
]
},
......................................
{
"name":"Catalog NameN",
"children":[
{
"name": "productName1ForCatalogNameN",
.....................
etc
},
{
"name": "productName2ForCatalogNameN"
.....................
},
........................
{
"name": "productNameNForCatalogNameN"
.....................
}
]
},
]
}
]
}
Many thanks.

CREATE
(b:Brand {name: 'ANTIINFECTIEUX',brand:'ANTIINFECTIEUX',description :'ANTIINFECTIEUX' }),
(b)<-[:IS_A_CATALOG_BELONGING_TO]-(c:Catalog {name: 'ANTIBACTERIENS',catalogName:'ANTIBACTERIENS',brand:'ANTIINFECTIEUX',brandIdentifier:'ANTIINFECTIEUX',description :'ANTIBACTERIENS'}),
(c)<-[:IS_A_PRODUCT_BELONGING_TO_THAT_CATALOG]-(p:Product {name: 'Amikacine',productName:'Amikacine',brand:'ANTIINFECTIEUX',brandIdentifier:'ANTIINFECTIEUX',catalog:'ANTIBACTERIENS',catalogIdentifier:'ANTIBACTERIENS',description :'Amikacine',DCI:'Amikacine', Dosage:'1g', Forme:'Inj', Case:'false', Poste:'false', CTRE:'false', CHR:'true', CHN:'true', Reference:'Amiklin', price:'200.75', stock:'500',NumeroDeLot:'87',CodeBarre:'87878787878787878',QuantiteDemandee:'50'}),
(p)-[:IS_A_PRODUCT_BELONGING_TO_THAT_BRAND]->(b),
(c)<-[:IS_A_PRODUCT_BELONGING_TO_THAT_CATALOG]-(p2:Product {name: 'Amoxicilline',productName:'Amoxicilline',brand:'ANTIINFECTIEUX',brandIdentifier:'ANTIINFECTIEUX',catalog:'ANTIBACTERIENS',catalogIdentifier:'ANTIBACTERIENS',description :'Amoxicilline',DCI:'Amoxicilline', Dosage:'500mg', Forme:'Gélules', Case:'false', Poste:'true', CTRE:'true', CHR:'true', CHN:'true', Reference:'Clamoxyl', price:'250.75', stock:'700',NumeroDeLot:'8777',CodeBarre:'998898979797',QuantiteDemandee:'50'}),
(p2)-[:IS_A_PRODUCT_BELONGING_TO_THAT_BRAND]->(b),
(b1:Brand {name: 'ANESTHESIQUES',brand:'ANESTHESIQUES',description :'ANESTHESIQUES' }),
(b1)<-[:IS_A_CATALOG_BELONGING_TO]-(c1:Catalog {name: 'Anesthesiques_Generaux_et_Gaz_medicaux',catalogName:'Anesthesiques_Generaux_et_Gaz_medicaux',brand:'ANESTHESIQUES',brandIdentifier:'ANESTHESIQUES',description :'Anesthésiques généraux et Gaz médicaux'}),
(c1)<-[:IS_A_PRODUCT_BELONGING_TO_THAT_CATALOG]-(p1:Product {name: 'Kétamine',productName:'Kétamine',brand:'ANESTHESIQUES',brandIdentifier:'ANESTHESIQUES',catalog:'Anesthesiques_Generaux_et_Gaz_medicaux',catalogIdentifier:'Anesthesiques_Generaux_et_Gaz_medicaux',description :'Kétamine',DCI:'Kétamine', Dosage:'50mg/amp', Forme:'Inj', Case:'false', Poste:'false', CTRE:'true', CHR:'true', CHN:'true', Reference:'Kétalar', price:'900.75', stock:'300',NumeroDeLot:'677',CodeBarre:'5454578788',QuantiteDemandee:'10'}),
(p1)-[:IS_A_PRODUCT_BELONGING_TO_THAT_BRAND]->(b1)
The request that fulfilled my needs
MATCH PATH = (Brand:Brand)<-[*]-(Catalog:Catalog)<-[*]-(Product:Product)
with collect(PATH) as paths
call apoc.convert.toTree(paths) yield value
return value
From neo4j 3.5.0 + and up the signature of apoc.convert.toTree
we could include/exclude properties,relationships and as has 3 arguments
[paths],[lowerCaseRels=true]) | creates a stream of nested documents representing the at least one root of these paths
like in the below
To exclude we prefix with -
apoc.convert.toTree(ps, true,{nodes: {Catalog: ['-name']},
relationships: {subcategory:['-id']}})
To include
apoc.convert.toTree(ps, true,{nodes: {Catalog: ['name']},
relationships: {subcategory:['id']}})
but Unfortunately there is no way to remove _id _type from cypher result See that link [https://stackoverflow.com/questions/56288402/is-there-a-way-to-remove-id-type-from-cypher-result]1

Related

Returning tasks for each session in Neo4J (list of lists)

I'm using Neo4J for a mentor platform I'm building and I'm stumped by the following:
Given the following nodes and properties:
Mentor{ login, ... }
Mentee{ login, ... }
Session{ notes, ... }
Task{ complete, name }
And the following associations:
// each session has 1 mentor and 1 mentee
(Mentor)<-[:HAS]-(Session)-[:HAS]->(Mentee)
// each task is FOR one person (a Mentor or Mentee)
// each task is FROM one Session
(Session)<-[:FROM]-(Task)-[:FOR]->(Mentor or Mentee)
What's the best way to query this data to produce an API response in the following shape? Similarly, is this a reasonable way to model the data? Maybe something with coalesce?
{
mentor: { login: '...', /* ... */ },
mentee: { login: '...', /* ... */ },
sessions: [
{
notes,
/* ... */
mentorTasks: [{ id, name, complete }],
menteeTasks: [{ id, name, complete }]
]
I first tried:
MATCH (mentor:Mentor{ github: "mentorlogin" })
MATCH (session:Session)-[:HAS]->(mentee:Mentee{ github: "menteelogin" })
OPTIONAL MATCH (mentor)<-[:FOR]-(mentorTask:Task)-[:FROM]->(session)
OPTIONAL MATCH (mentee)<-[:FOR]-(menteeTask:Task)-[:FROM]->(session)
RETURN
mentor,
mentee,
session,
COLLECT(DISTINCT mentorTask) as mentorTasks,
COLLECT(DISTINCT menteeTask) as menteeTasks
ORDER BY session.date DESC
But that's janky - The mentor and mentee data is returned many times, and it's completely gone if the mentee has no sessions.
This seems more appropriate, but I'm not sure how to fold in the tasks:
MATCH (mentor:Mentor{ github: "mentorlogin" })
MATCH (mentee:Mentee{ github: "menteelogin })
OPTIONAL MATCH (session:Session)-[:HAS]->(mentee)
OPTIONAL MATCH (mentor)<-[:FOR]-(mentorTask:Task)-[:FROM]->(session)
OPTIONAL MATCH (mentee)<-[:FOR]-(menteeTask:Task)-[:FROM]->(session)
RETURN
mentor,
mentee,
COLLECT(DISTINCT session) as sessions
EDIT: Working! thanks to a prompt response from Graphileon. I made a few modifications:
changed MATCH statement so it returns the mentor and mentee even if there are no sessions
sort sessions by date (most recent first)
return all node properties, instead of whitelisting
MATCH (mentor:Mentor{ github: $mentorGithub })
MATCH (mentee:Mentee{ github: $menteeGithub })
RETURN DISTINCT {
mentor: mentor{ .*, id: toString(id(mentor)) },
mentee: mentee{ .*, id: toString(id(mentee)) },
sessions: apoc.coll.sortMaps([(mentor:Mentor)<-[:HAS]-(session:Session)-[:HAS]->(mentee:Mentee) |
session{
.*,
id: toString(id(session)),
mentorTasks: [
(session)<-[:FROM]-(task:Task)-[:FOR]->(mentor) |
task{ .*, id: toString(id(task)) }
],
menteeTasks: [
(session)<-[:FROM]-(task:Task)-[:FOR]->(mentee) |
task{ .*, id: toString(id(task)) }
]
}
], "date")
} AS result
Presuming you would have these data:
You can do something along these lines, with nested pattern comprehensions
MATCH (mentor:Mentor)<-[:HAS]-(:Session)-[:HAS]->(mentee:Mentee)
RETURN DISTINCT {
mentor: {id:id(mentor), name: mentor.name},
mentee: {id:id(mentee), name: mentee.name},
sessions: [(mentor:Mentor)<-[:HAS]-(session:Session)-[:HAS]->(mentee:Mentee) |
{ id: id(session),
name: session.name,
mentorTasks: [(session)<-[:FROM]-(task:Task)-[:FOR]->(mentor) |
{id:id(task), name: task.name}
],
menteeTasks: [(session)<-[:FROM]-(task:Task)-[:FOR]->(mentee) |
{id:id(task), name: task.name}
]
}
]
} AS myResult
returning
{
"mentor": {
"name": "Mentor Jill",
"id": 211
},
"sessions": [
{
"menteeTasks": [
{
"id": 223,
"name": "Task D"
},
{
"id": 220,
"name": "Task C"
},
{
"id": 219,
"name": "Task B"
}
],
"name": "Session 1",
"id": 208,
"mentorTasks": [
{
"id": 213,
"name": "Task A"
}
]
}
],
"mentee": {
"name": "Mentee Joe",
"id": 212
}
}
Note that using the pattern comprehensions, you can avoid the OPTIONAL matches. If a pattern comprehension does not find anything, it returns []

How to create dynamic node relation in neo4j for dynamic data?

I was able to create author nodes directly from the json file . But the challenge is on what basis or how we have to link the data. Linking "Author" to "organization". since the data is dynamic we cannot generalize it. I have tried with using csv file but, it fails the conditions when dynamic data is coming. For example one json record contain 2 organization and 3 authors, next record will be different. Different json record have different author and organization to link. organization/1 represent organization1 and organization/2 represents organization 2. Any help or hint will be great. Thank you. Please find the json file below.
"Author": [
{
"seq": "3",
"type": "abc",
"identifier": [
{
"idtype:auid": "10000000"
}
],
"familyName": "xyz",
"indexedName": "MI",
"givenName": "T",
"preferredName": {
"familyName": "xyz1",
"givenName": "a",
"initials": "T.",
"indexedName": "bT."
},
"emailAddressList": [],
"degrees": [],
"#id": "https:abc/2009127993/author/person/3",
"hasAffiliation": [
"https:abc/author/organization/1"
],
"organization": [
[
{
"identifier": [
{
"#type": "idtype:uuid",
"#subtype": "idsubtype:affiliationInstanceId",
"#value": "aff2"
},
{
"#type": "idtype:OrgDB",
"#subtype": "idsubtype:afid",
"#value": "12345"
},
{
"#type": "idtype:OrgDB",
"#subtype": "idsubtype:dptid"
}
],
"organizations": [],
"addressParts": [],
"sourceText": "",
"text": " Medical University School of Medicine",
"#id": "https:abc/author/organization/1"
}
],
[
{
"identifier": [
{
"#type": "idtype:uuid",
"#subtype": "idsubtype:affiliationInstanceId",
"#value": "aff1"
},
{
"#type": "idtype:OrgDB",
"#subtype": "idsubtype:afid",
"#value": "7890"
},
{
"#type": "idtype:OrgDB",
"#subtype": "idsubtype:dptid"
}
],
"organizations": [],
"addressParts": [],
"sourceText": "",
"text": "K University",
"#id": "https:efg/author/organization/2"
}
]
Hi I see that Organisation is part of the Author data, so you have to model it like wise. So for instance (Author)-[:AFFILIATED_WITH]->(Organisation)
When you use apoc.load.json which supports a stream of author objects you can load the data.
I did some checks on your JSON structure with this cypher query:
call apoc.load.json("file:///Users/keesv/work/check.json") yield value
unwind value as record
WITH record.Author as author
WITH author.identifier[0].`idtype:auid` as authorId,author, author.organization[0] as organizations
return authorId, author, organizations
To get this working you will need to create include apoc in the plugins directory, and add the following two lines in the apoc.conf file (create one if it is not there) in the 'conf' directory.
apoc.import.file.enabled=true
apoc.import.file.use_neo4j_config=false
I also see a nested array for the organisations in the output why is that and what is the meaning of that?
And finally I see also in the JSON that an organisation can have a reference to other organisations.
explanation
In my query I use UNWIND to unwind the base Author array. This means you get for every author a 'record' to work with.
With a MERGE or CREATE statement you can now create an Author Node with the correct properties. With the FOREACH construct you can walk over all the Organization entry and create/merge an Organization node and create the relation between the Author and the Organization.
here an 'psuedo' example
call apoc.load.json("file:///Users/keesv/work/check.json") yield value
unwind value as record
WITH record.Author as author
WITH author.identifier[0].`idtype:auid` as authorId,author, author.organization[0] as organizations
// creating the Author node
MERGE (a:Author { id: authorId })
SET a.familyName = author.familyName
...
// walk over the organizations
// determine
FOREACH (org in organizations |
MERGE (o:Organization { id: ... })
SET o.name = org.text
...
MERGE (a)-[:AFFILIATED_WITH]->(o)
// if needed you can also do a nested FOREACH here to process the Org Org relationship
)
Here is the JSON file I used I had to change something at the start and the end
[
{
"Author":{
"seq":"3",
"type":"abc",
"identifier":[
{
"idtype:auid":"10000000"
}
],
"familyName":"xyz",
"indexedName":"MI",
"givenName":"T",
"preferredName":{
"familyName":"xyz1",
"givenName":"a",
"initials":"T.",
"indexedName":"bT."
},
"emailAddressList":[
],
"degrees":[
],
"#id":"https:abc/2009127993/author/person/3",
"hasAffiliation":[
"https:abc/author/organization/1"
],
"organization":[
[
{
"identifier":[
{
"#type":"idtype:uuid",
"#subtype":"idsubtype:affiliationInstanceId",
"#value":"aff2"
},
{
"#type":"idtype:OrgDB",
"#subtype":"idsubtype:afid",
"#value":"12345"
},
{
"#type":"idtype:OrgDB",
"#subtype":"idsubtype:dptid"
}
],
"organizations":[
],
"addressParts":[
],
"sourceText":"",
"text":" Medical University School of Medicine",
"#id":"https:abc/author/organization/1"
}
],
[
{
"identifier":[
{
"#type":"idtype:uuid",
"#subtype":"idsubtype:affiliationInstanceId",
"#value":"aff1"
},
{
"#type":"idtype:OrgDB",
"#subtype":"idsubtype:afid",
"#value":"7890"
},
{
"#type":"idtype:OrgDB",
"#subtype":"idsubtype:dptid"
}
],
"organizations":[
],
"addressParts":[
],
"sourceText":"",
"text":"K University",
"#id":"https:efg/author/organization/2"
}
]
]
}
}
]
IMPORTANT create unique constraints for Author.id and Organization.id!!
In this way you can process any json file with an unknown number of author elements and an unknown number of affiliated organisations

How to query all eventMessages from a specific event on Microsoft Graph API

I'm trying to get all the eventMessages that belongs to a specific event. However I'm not being able to.
I'm querying the me/messages with the $expand=microsoft.graph.eventMessage/event and trying to filter on this relationship:
https://graph.microsoft.com/v1.0/me/messages?$expand=microsoft.graph.eventMessage/event&filter=event/id eq 'eventID'
However, I'm getting the following error
{
"error": {
"code": "RequestBroker--ParseUri",
"message": "Could not find a property named 'event' on type 'Microsoft.OutlookServices.Message'.",
"innerError": {
"request-id": "cb211061-802b-4657-a191-f06c449897d1",
"date": "2019-05-16T14:01:43"
}
}
}
But looking at their example of expanding the message with the event, there is an event property on the return of the API (see returned JSON below).
HTTP/1.1 200 OK
Content-type: application/json
{
"#odata.context":"https://graph.microsoft.com/v1.0/$metadata#users('8fd6e83b-3cc0-4bf0-8b26-950f4d7110f6')/messages/$entity",
"#odata.type":"#microsoft.graph.eventMessage",
"#odata.etag":"W/\"CwAAABYAAABXlB/SL0N4Q6g6o+jSYAEuAAAImkVF\"",
"id":"AAMkADYAAAImV_jAAA=",
"createdDateTime":"2017-12-27T21:54:55Z",
"lastModifiedDateTime":"2017-12-27T23:26:38Z",
"changeKey":"CwAAABYAAABXlB/SL0N4Q6g6o+jSYAEuAAAImkVF",
"categories":[
],
"receivedDateTime":"2017-12-27T21:54:55Z",
"sentDateTime":"2017-12-27T21:54:54Z",
"hasAttachments":false,
"internetMessageId":"<MWHPR1301MB211042CF#MWHPR1301MB2110.namprd13.prod.outlook.com>",
"subject":"Kick off planning",
"bodyPreview":"Let's collect opinions from our teams and organize action items.",
"importance":"normal",
"parentFolderId":"AQMkADYAS4AAAIBDAAAAA==",
"conversationId":"AAQkADYRuffB3wDlPn-ReFZarI60=",
"isDeliveryReceiptRequested":null,
"isReadReceiptRequested":false,
"isRead":false,
"isDraft":false,
"webLink":"https://outlook.office365.com/owa/?ItemID=AAMkADYAAAImV%2BjAAA%3D&exvsurl=1&viewmodel=ReadMessageItem",
"inferenceClassification":"focused",
"meetingMessageType":"meetingRequest",
"body":{
"contentType":"html",
"content":"<html>\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n<meta content=\"text/html; charset=us-ascii\">\r\n<style type=\"text/css\" style=\"display:none\">\r\n<!--\r\np\r\n\t{margin-top:0;\r\n\tmargin-bottom:0}\r\n-->\r\n</style>\r\n</head>\r\n<body dir=\"ltr\">\r\n<div id=\"divtagdefaultwrapper\" dir=\"ltr\" style=\"font-size:12pt; color:#000000; font-family:Calibri,Helvetica,sans-serif\">\r\n<p style=\"margin-top:0; margin-bottom:0\">Let's collect opinions from our teams and organize action items.<br>\r\n</p>\r\n</div>\r\n</body>\r\n</html>\r\n"
},
"sender":{
"emailAddress":{
"name":"Administrator",
"address":"admin#contoso.onmicrosoft.com"
}
},
"from":{
"emailAddress":{
"name":"Administrator",
"address":"admin#contoso.onmicrosoft.com"
}
},
"toRecipients":[
{
"emailAddress":{
"name":"Alex Wilber",
"address":"AlexW#contoso.onmicrosoft.com"
}
}
],
"ccRecipients":[
],
"bccRecipients":[
],
"replyTo":[
],
"event#odata.context":"https://graph.microsoft.com/v1.0/$metadata#users('8fd6e83b-3cc0-4bf0-8b26-950f4d7110f6')/messages('AAMkADYAAAImV_jAAA%3D')/microsoft.graph.eventMessage/event/$entity",
"event":{
"#odata.etag":"W/\"V5Qf0i9DeEOoOqPo0mABLgAACJpBWg==\"",
"id":"AAMkADYAAAImVu6AAA=",
"createdDateTime":"2017-12-27T21:54:55.2624551Z",
"lastModifiedDateTime":"2017-12-27T22:19:16.6667889Z",
"changeKey":"V5Qf0i9DeEOoOqPo0mABLgAACJpBWg==",
"categories":[
],
"originalStartTimeZone":"Pacific Standard Time",
"originalEndTimeZone":"Pacific Standard Time",
"iCalUId":"040000008200E00074C5B7101A82E00800000000A2A6F3535D7FD3010000000000000000100000003D770E2E8974F44B9471BDB348097FE3",
"reminderMinutesBeforeStart":15,
"isReminderOn":true,
"hasAttachments":false,
"subject":"Kick off planning",
"bodyPreview":"Let's collect opinions from our teams and organize action items.",
"importance":"normal",
"sensitivity":"normal",
"isAllDay":false,
"isCancelled":false,
"isOrganizer":false,
"responseRequested":true,
"seriesMasterId":null,
"showAs":"tentative",
"type":"singleInstance",
"webLink":"https://outlook.office365.com/owa/?itemid=AAMkADYAAAImVu6AAA%3D&exvsurl=1&path=/calendar/item",
"onlineMeetingUrl":null,
"responseStatus":{
"response":"tentativelyAccepted",
"time":"2017-12-27T22:19:12.6197462Z"
},
"body":{
"contentType":"html",
"content":"<html>\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n<meta content=\"text/html; charset=us-ascii\">\r\n<style type=\"text/css\" style=\"display:none\">\r\n<!--\r\np\r\n\t{margin-top:0;\r\n\tmargin-bottom:0}\r\n-->\r\n</style>\r\n</head>\r\n<body dir=\"ltr\">\r\n<div id=\"divtagdefaultwrapper\" dir=\"ltr\" style=\"font-size:12pt; color:#000000; font-family:Calibri,Helvetica,sans-serif\">\r\n<p style=\"margin-top:0; margin-bottom:0\">Let's collect opinions from our teams and organize action items.<br>\r\n</p>\r\n</div>\r\n</body>\r\n</html>\r\n"
},
"start":{
"dateTime":"2018-02-02T22:00:00.0000000",
"timeZone":"UTC"
},
"end":{
"dateTime":"2018-02-02T23:00:00.0000000",
"timeZone":"UTC"
},
"location":{
"displayName":"Mt. Hood"
},
"recurrence":null,
"attendees":[
{
"type":"required",
"status":{
"response":"none",
"time":"0001-01-01T00:00:00Z"
},
"emailAddress":{
"name":"Administrator",
"address":"admin#contoso.onmicrosoft.com"
}
},
{
"type":"required",
"status":{
"response":"tentativelyAccepted",
"time":"0001-01-01T00:00:00Z"
},
"emailAddress":{
"name":"Alex Wilber",
"address":"AlexW#contoso.onmicrosoft.com"
}
}
],
"organizer":{
"emailAddress":{
"name":"Administrator",
"address":"admin#contoso.onmicrosoft.com"
}
}
}
}
I would expect that the above filter would make the API return only the messages belonging to that event. However, it's giving me a 400 and I think it might be because of the syntax on how to filter in this specific case, but I can't find anything on their documentation about this. Does anybody know how to perform this kind of filter on the graph API?

What am I doing wrong on using JSON Decoder

I'm trying to decode a JSON, however, my breakpoint goes straight to the Catch Statement instead of print the decode data. What am I doing wrong? What I tried was create another Struct where I unpacked all the Array of Data from JSON, I already did it on previous projects, but for this one something else is wrong. For this project I gotta parse the following properties:
name
abv
url_image
JSON
[
{
"id":1,
"name":"Buzz",
"tagline":"A Real Bitter Experience.",
"first_brewed":"09/2007",
"description":"A light, crisp and bitter IPA brewed with English and American hops. A small batch brewed only once.",
"image_url":"https://images.punkapi.com/v2/keg.png",
"abv":4.5,
"ibu":60,
"target_fg":1010,
"target_og":1044,
"ebc":20,
"srm":10,
"ph":4.4,
"attenuation_level":75,
"volume":{
"value":20,
"unit":"liters"
},
"boil_volume":{
"value":25,
"unit":"liters"
},
"method":{
"mash_temp":[
{
"temp":{
"value":64,
"unit":"celsius"
},
"duration":75
}
],
"fermentation":{
"temp":{
"value":19,
"unit":"celsius"
}
},
"twist":null
},
"ingredients":{
"malt":[
{
"name":"Maris Otter Extra Pale",
"amount":{
"value":3.3,
"unit":"kilograms"
}
},
{
"name":"Caramalt",
"amount":{
"value":0.2,
"unit":"kilograms"
}
},
{
"name":"Munich",
"amount":{
"value":0.4,
"unit":"kilograms"
}
}
],
"hops":[
{
"name":"Fuggles",
"amount":{
"value":25,
"unit":"grams"
},
"add":"start",
"attribute":"bitter"
},
{
"name":"First Gold",
"amount":{
"value":25,
"unit":"grams"
},
"add":"start",
"attribute":"bitter"
},
{
"name":"Fuggles",
"amount":{
"value":37.5,
"unit":"grams"
},
"add":"middle",
"attribute":"flavour"
},
{
"name":"First Gold",
"amount":{
"value":37.5,
"unit":"grams"
},
"add":"middle",
"attribute":"flavour"
},
{
"name":"Cascade",
"amount":{
"value":37.5,
"unit":"grams"
},
"add":"end",
"attribute":"flavour"
}
],
"yeast":"Wyeast 1056 - American Ale™"
},
"food_pairing":[
"Spicy chicken tikka masala",
"Grilled chicken quesadilla",
"Caramel toffee cake"
],
"brewers_tips":"The earthy and floral aromas from the hops can be overpowering. Drop a little Cascade in at the end of the boil to lift the profile with a bit of citrus.",
"contributed_by":"Sam Mason <samjbmason>"
},
{
"id":2,
"name":"Trashy Blonde",
"tagline":"You Know You Shouldn't",
"first_brewed":"04/2008",
"description":"A titillating, neurotic, peroxide punk of a Pale Ale. Combining attitude, style, substance, and a little bit of low self esteem for good measure; what would your mother say? The seductive lure of the sassy passion fruit hop proves too much to resist. All that is even before we get onto the fact that there are no additives, preservatives, pasteurization or strings attached. All wrapped up with the customary BrewDog bite and imaginative twist.",
"image_url":"https://images.punkapi.com/v2/2.png",
"abv":4.1,
"ibu":41.5,
"target_fg":1010,
"target_og":1041.7,
"ebc":15,
"srm":15,
"ph":4.4,
"attenuation_level":76,
"volume":{
"value":20,
"unit":"liters"
},
"boil_volume":{
"value":25,
"unit":"liters"
},
"method":{
"mash_temp":[
{
"temp":{
"value":69,
"unit":"celsius"
},
"duration":null
}
],
"fermentation":{
"temp":{
"value":18,
"unit":"celsius"
}
},
"twist":null
},
"ingredients":{
"malt":[
{
"name":"Maris Otter Extra Pale",
"amount":{
"value":3.25,
"unit":"kilograms"
}
},
{
"name":"Caramalt",
"amount":{
"value":0.2,
"unit":"kilograms"
}
},
{
"name":"Munich",
"amount":{
"value":0.4,
"unit":"kilograms"
}
}
],
"hops":[
{
"name":"Amarillo",
"amount":{
"value":13.8,
"unit":"grams"
},
"add":"start",
"attribute":"bitter"
},
{
"name":"Simcoe",
"amount":{
"value":13.8,
"unit":"grams"
},
"add":"start",
"attribute":"bitter"
},
{
"name":"Amarillo",
"amount":{
"value":26.3,
"unit":"grams"
},
"add":"end",
"attribute":"flavour"
},
{
"name":"Motueka",
"amount":{
"value":18.8,
"unit":"grams"
},
"add":"end",
"attribute":"flavour"
}
],
"yeast":"Wyeast 1056 - American Ale™"
},
"food_pairing":[
"Fresh crab with lemon",
"Garlic butter dipping sauce",
"Goats cheese salad",
"Creamy lemon bar doused in powdered sugar"
],
"brewers_tips":"Be careful not to collect too much wort from the mash. Once the sugars are all washed out there are some very unpleasant grainy tasting compounds that can be extracted into the wort.",
"contributed_by":"Sam Mason <samjbmason>"
},
Network Folder
import Foundation
import Alamofire
func getApiData(){
guard let urlString = URL(string: "https://api.punkapi.com/v2/beers") else {
print("URL Error")
return
}
Alamofire.request(urlString).responseJSON { response in
if response.data == response.data{
do{
let decoder = try JSONDecoder().decode(Data.self, from: response.data!)
print(decoder)
}catch{
print(error)
}
}else{print("API Response is Empty")}
}
}
Model Folder
import Foundation
struct Data:Decodable {
let cervejas : [Cerveja]
}
struct Cerveja:Decodable{
let name:String?
let abv:String?
let image_url:String?
}
Try with
let models = try JSONDecoder().decode([Cerveja].self, from: response.data!)
Change the Cerveja class to
let abv:Double?
after successful decoding access them like
for model in models {
print(model.name)
}

Filter result based on a count of inner data

I am building my search query for some listing data. As part of the search people can ask for multiple rooms which sleeps a min amount of people, ie two rooms which sleep 2 and 3 people.
Im not sure how I can perform that with a filter.
Here is a shortened search query so far.
{
"query":{
"filtered":{
"query":{
"match_all":{}
}
}
},
"filter":{
"and":
[
{
"term":{
"status":"live"
}
},
{
"geo_bounding_box":{
"location":{
"top_left":"60.856553, -8.64935719999994",
"bottom_right":"49.8669688, 1.76270959999999"
}
}
}
,{
"range":{
"bedrooms":{
"gte":"2"
}
}
}
]
}
,
"size":10
}
Test Data
{
"took":1,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"failed":0
},
"hits":{
"total":3,
"max_score":1.0,
"hits":[
{
"_index":"listings",
"_type":"listing",
"_id":"1",
"_score":1.0,
"_source":{
"name:":"Listing One",
"address1":"Some Street",
"bedrooms":2,
"city":"A City",
"id":1,
"refno":"FI451",
"user_id":1,
"rooms":[
{
"bathroom":"Shared bathroom with bath",
"double_standard":null,
"id":5,
"single":2,
"sleeps":2,
"title":"Twinny",
},
{
"bathroom":"Ensuite with bath",
"double_king_size":1,
"double_standard":1,
"id":1,
"single":null,
"sleeps":2,
"title":"Double Ensuite Room",
}
]
}
},
{
"_index":"listings",
"_type":"listing",
"_id":"2",
"_score":1.0,
"_source":{
"name":"Listing Two",
"address1":"Some Street",
"bedrooms":2,
"city":"A City",
"id":2,
"refno":"BL932",
"user_id":1,
"rooms":[
{
"bathroom":"Ensuite with bath",
"double_standard":1,
"id":4,
"single":1,
"sleeps":3,
"title":"Family Room",
},
{
"bathroom":"Ensuite with shower",
"double_standard":1,
"id":2,
"single":null,
"sleeps":2,
"title":"Single Room",
}
]
}
},
{
"_index":"listings",
"_type":"listing",
"_id":"3",
"_score":1.0,
"_source":{
"name":"Listing Three",
"address1":"Another Address",
"bedrooms":1,
"city":"Your City",
"id":3,
"refno":"TE2116",
"user_id":1,
"rooms":[
{
"bathroom":"Ensuite with shower",
"double_king_size":null,
"double_standard":1,
"id":3,
"single":1,
"sleeps":3,
"title":"Family Room",
}
]
}
}
]
}
}
If you look at my data I have 3 listings, two of them have multiple rooms (Listing One & Two) but only Listing Two would match my search, Reason it has one room with that sleeps two and the other sleeps three.
Is it possible to perform this query with elasticsearch?
If what you want is "Find all listings where a bedroom sleeps 2 AND another bedroom sleeps 3", this query will work. It makes one big assumptions: that you are using inner objects, and not the Nested data type.
This query is using the fact that inner objects are collapsed into a single field, causing "rooms.sleeps" to equal [2,3] for the desired field. Since the field is collapsed into a single array, a simple Terms query will match them. When you change the execution mode to And, it forces both 2 and 3 to be matched.
The caveat is that a room that has [2,3,4] will also be matched.
I've omitted the geo and status portion since that data wasn't provided in the source documents.
{
"query": {
"filtered": {
"query": {
"match_all": {}
}
}
},
"filter": {
"and": [
{
"range": {
"bedrooms": {
"gte": "2"
}
}
},
{
"terms": {
"rooms.sleeps": [2,3],
"execution": "and"
}
}
]
},
"size": 10
}
As far as I know the filter has to be a sibling of the query inside the filtered element. See: http://www.elasticsearch.org/guide/reference/query-dsl/filtered-query/
If you combine that with Zach's solution it should work.
{
"query":
{
"filtered":
{
"query":
{
"match_all":{}
},
"filter":
{
"put" : "your filter here"
}
}
}
}

Resources