I'm working with The AWS Command Line Interface for DynamoDB.
When we query an item, we get a very detailed JSON output. You get something like this (it has been built from the get-item
in order to be almost exhaustive (the NULL
type has been omitted) aws command line help:
{ "Count": 1, "Items": [ { "Id": { "S": "app1" }, "Parameters": { "M": { "nfs": { "M": { "IP" : { "S" : "172.16.0.178" }, "defaultPath": { "S": "/mnt/ebs/" }, "key": { "B": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk" }, "activated": { "BOOL": true } } }, "ws" : { "M" : { "number" : { "N" : "5" }, "values" : { "L" : [ { "S" : "12253456346346"}, { "S" : "23452353463464"}, { "S" : "23523453461232"}, { "S" : "34645745675675"}, { "S" : "46456745757575"} ] } } } } }, "Oldtypes": { "typeSS" : {"SS" : ["foo", "bar", "baz"]}, "typeNS" : {"NS" : ["0", "1", "2", "3", "4", "5"]}, "typeBS" : {"BS" : ["VGVybWluYXRvcgo=", "VGVybWluYXRvciAyOiBKdWRnbWVudCBEYXkK", "VGVybWluYXRvciAzOiBSaXNlIG9mIHRoZSBNYWNoaW5lcwo=", "VGVybWluYXRvciA0OiBTYWx2YXRpb24K","VGVybWluYXRvciA1OiBHZW5lc2lzCg=="]} } } ], "ScannedCount": 1, "ConsumedCapacity": null }
Is there any way to get a simpler output for the Items
part? Like this:
{ "ConsumedCapacity": null, "Count": 1, "Items": [ { "Id": "app1", "Parameters": { "nfs": { "IP": "172.16.0.178", "activated": true, "defaultPath": "/mnt/ebs/", "key": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk" }, "ws": { "number": 5, "values": ["12253456346346","23452353463464","23523453461232","34645745675675","46456745757575"] } }, "Oldtypes": { "typeBS": ["VGVybWluYXRvcgo=", "VGVybWluYXRvciAyOiBKdWRnbWVudCBEYXkK", "VGVybWluYXRvciAzOiBSaXNlIG9mIHRoZSBNYWNoaW5lcwo=", "VGVybWluYXRvciA0OiBTYWx2YXRpb24K", "VGVybWluYXRvciA1OiBHZW5lc2lzCg=="], "typeNS": [0, 1, 2, 3, 4, 5], "typeSS": ["foo","bar","baz"] } } ], "ScannedCount": 1 }
There is nothing helpful in the dynamodb - AWS CLI 1.7.10 documentation.
We must get the result from the command line. I'm willing to use other command line tools like jq
if necessary, but such a jq
mapping appears to complicated to me.
Update 1: jq
based solution (with help from DanielH's answer)
With jq
it is easy, but not quite pretty, you can do something like:
$> aws dynamodb query --table-name ConfigCatalog --key-conditions '{ "Id" : {"AttributeValueList": [{"S":"app1"}], "ComparisonOperator": "EQ"}}' | jq -r '.Items[0].Parameters.M."nfs#IP".S'
Result will be: 172.16.0.178
The jq
-r
option gives you a raw output.
Update 2: jq
based solution (with help from @jeff-mercado)
Here is an updated and commented version of Jeff Mercado jq
function to unmarshall DynamoDB output. It will give you the expected output:
$> cat unmarshal_dynamodb.jq def unmarshal_dynamodb: # DynamoDB string type (objects | .S) # DynamoDB blob type // (objects | .B) # DynamoDB number type // (objects | .N | strings | tonumber) # DynamoDB boolean type // (objects | .BOOL) # DynamoDB map type, recursion on each item // (objects | .M | objects | with_entries(.value |= unmarshal_dynamodb)) # DynamoDB list type, recursion on each item // (objects | .L | arrays | map(unmarshal_dynamodb)) # DynamoDB typed list type SS, string set // (objects | .SS | arrays | map(unmarshal_dynamodb)) # DynamoDB typed list type NS, number set // (objects | .NS | arrays | map(tonumber)) # DynamoDB typed list type BS, blob set // (objects | .BS | arrays | map(unmarshal_dynamodb)) # managing others DynamoDB output entries: "Count", "Items", "ScannedCount" and "ConsumedCapcity" // (objects | with_entries(.value |= unmarshal_dynamodb)) // (arrays | map(unmarshal_dynamodb)) # leaves values // . ; unmarshal_dynamodb
If you save the DynamoDB
query output to a file, lets say ddb-query-result.json
, you can execute to get desired result:
$> jq -f unmarshal_dynamodb.jq ddb-query-result.json
Query Sort Key DescendingIf the sort key is a number, the results will be returned in numerical order. Else, the results will be returned in UTF-8 bytes order. The sort order is set to ascending by default. However, you can set the ScanIndexForward parameter to false if you need the sort order to be Descending.
In a Query operation, DynamoDB retrieves the items in sorted order, and then processes the items using KeyConditionExpression and any FilterExpression that might be present. Only then are the Query results sent back to the client. A Query operation always returns a result set.
Expression attribute values in Amazon DynamoDB are substitutes for the actual values that you want to compare—values that you might not know until runtime. An expression attribute value must begin with a colon ( : ) and be followed by one or more alphanumeric characters.
You can decode the values recursively with a well crafted function. It looks like the key names correspond to a type:
S -> string N -> number M -> map
Handle each of the cases you want to decode if possible, otherwise filter it out. You can make use of the various type filters and the alternative operator to do so.
$ cat input.json { "Count": 1, "Items": [ { "Id": { "S": "app1" }, "Parameters": { "M": { "nfs#IP": { "S": "192.17.0.13" }, "maxCount": { "N": "1" }, "nfs#defaultPath": { "S": "/mnt/ebs/" } } } } ], "ScannedCount": 1, "ConsumedCapacity": null }
$ cat ~/.jq def decode_ddb: def _sprop($key): select(keys == [$key])[$key]; # single property objects only ((objects | { value: _sprop("S") }) # string (from string) // (objects | { value: _sprop("B") }) # blob (from string) // (objects | { value: _sprop("N") | tonumber }) # number (from string) // (objects | { value: _sprop("BOOL") }) # boolean (from boolean) // (objects | { value: _sprop("M") | map_values(decode_ddb) }) # map (from object) // (objects | { value: _sprop("L") | map(decode_ddb) }) # list (from encoded array) // (objects | { value: _sprop("SS") }) # string set (from string array) // (objects | { value: _sprop("NS") | map(tonumber) }) # number set (from string array) // (objects | { value: _sprop("BS") }) # blob set (from string array) // (objects | { value: map_values(decode_ddb) }) # all other non-conforming objects // (arrays | { value: map(decode_ddb) }) # all other non-conforming arrays // { value: . }).value # everything else ;
$ jq 'decode_ddb' input.json { "Count": 1, "Items": [ { "Id": "app1", "Parameters": { "nfs#IP": "192.17.0.13", "maxCount": 1, "nfs#defaultPath": "/mnt/ebs/" } } ], "ScannedCount": 1, "ConsumedCapacity": null }
Another way to achieve the post's goal would be to use a node.js
extension like node-dynamodb
or dynamodb-marshaler
and build a node
command line tool.
Interesting tutorial to build a node.js
command line application with commander
package: Creating Your First Node.js Command-line Application
Here's a quick and dirty oneliner that reads one record from stdin and prints it in simplified form:
node -e 'console.log(JSON.stringify(require("aws-sdk").DynamoDB.Converter.unmarshall(JSON.parse(require("fs").readFileSync(0, "utf-8")))))'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With