Products by supplier in OData and jq
This is more of a note-to-self. I'm enjoying comparing resource requests in OData with the equivalent shaping with jq
. Here's a simple example.
With the Northwind OData v4 service there are Products and Suppliers. As an easy exercise I want to create a list of products by supplier.
With OData
With OData it's easy to follow the Products
navigation property in the Supplier
entity type (see the metadata for more info) to the Product
entity type.
With an OData QUERY operation it's straightforward, using the $expand
and $select
system query options (with extra whitespace for readability):
https://services.odata.org
/v4/northwind/northwind.svc/Suppliers
?$expand=Products($select=ProductName)
&$select=CompanyName
You can try out this OData request directly, and the JSON representation in the response looks something like this (shortened to the first two suppliers for brevity):
{
"@odata.context": "https://services.odata.org/V4/Northwind/Northwind.svc/$metadata#Suppliers(CompanyName,Products,Products(ProductName))",
"value": [
{
"CompanyName": "Exotic Liquids",
"Products": [
{
"ProductName": "Chai"
},
{
"ProductName": "Chang"
},
{
"ProductName": "Aniseed Syrup"
}
]
},
{
"CompanyName": "New Orleans Cajun Delights",
"Products": [
{
"ProductName": "Chef Anton's Cajun Seasoning"
},
{
"ProductName": "Chef Anton's Gumbo Mix"
},
{
"ProductName": "Louisiana Fiery Hot Pepper Sauce"
},
{
"ProductName": "Louisiana Hot Spiced Okra"
}
]
}
]
}
With jq
How might we do this in jq
? Let's see. First, let's grab some JSON data. To make it a little more interesting (i.e. without going directly to the supplier grouping) we'll start with the Products
entityset and expand the Supplier
navigation property like this:
https://services.odata.org
/v4/northwind/northwind.svc/Products
?$expand=Supplier
(Note for this simple example, I won't bother trying to consume all of the products by following the @odata.nextLink
s).
This gives a nice structure that we can dig into with jq
. It turns out to be quite simple, especially in the context of the recent process I followed in Exploring JSON with interactive jq.
Being an OData v4 entityset, the data is in the top level value
property, so we start with that, grouping by the ID of each product's supplier:
.value
| group_by(.SupplierID)
Then all we need to do is to reshape the resulting array, via map
, to product an object for each supplier, with a list of products:
.value
| group_by(.SupplierID)
| map(
{
CompanyName: first.Supplier.CompanyName,
Products: [.[].ProductName]
}
)
Here's a screenshot of this jq
invocation in action, against the OData JSON representation retrieved with the URL above.
You can see the results of the jq
filter, producing what we want (reduced for brevity):
[
{
"CompanyName": "Exotic Liquids",
"Products": [
"Chai",
"Chang",
"Aniseed Syrup"
]
},
{
"CompanyName": "New Orleans Cajun Delights",
"Products": [
"Chef Anton's Cajun Seasoning",
"Chef Anton's Gumbo Mix"
]
},
{
"CompanyName": "Grandma Kelly's Homestead",
"Products": [
"Grandma's Boysenberry Spread",
"Uncle Bob's Organic Dried Pears",
"Northwoods Cranberry Sauce"
]
}
]
You can examine how this works yourself courtesy of jq play.
Note: If we wanted to create the same shape as the OData output, with each product name as a value for a ProductName
property, this would just need a small change:
.value
| group_by(.SupplierID)
| map(
{
CompanyName: first.Supplier.CompanyName,
Products: map({ProductName})
}
)
This works because of the shortcut syntax available for jq
's object construction ({...}
) which is simply to use the name of the property (and you don't even need quotes):
{ProductName}
is the same as:
{"ProductName": .ProductName}