Reputation: 396
'm new to jq, and hoping to convert JSON below so that, for each object in the records array , the "Account" object is deleted and replaced with an "AccountID" field which has a the value of Account.Id. Assuming I don't know what the name of the field (eg. Account ) is prior to executing, so it Has to be dynamically included as an argument to --arg.
Contacts.json:
{
"records": [
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef1"
},
"Account": {
"attributes": {
"type": "Account",
"url": "/services/data/v51.0/sobjects/Account/asdf"
},
"Id": "asdf"
}
},
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef2"
},
"Account": {
"attributes": {
"type": "Account",
"url": "/services/data/v51.0/sobjects/Account/qwer"
},
"Id": "qwer"
}
}
]
}
to
{
"records": [
{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef1"
},
"AccountID": "asdf"
}
},{
"attributes": {
"type": "Contact",
"referenceId": "ContactRef2"
},
"AccountID": "qwer"
}
}
]
}
This example above is a little contrived because in actuality, I need to be able to dynamically name the ID field to be able to port the new JSON structure into destination system. For my use case, it's not always valid to tack "ID" onto the field name ( eg. Account .. ID ), so I passed the field names to --arg .
This is as close as I got.. but it's not quite there. and I suspect there is better way.
jq -c --arg field "Account" --arg field_name_id "AccountID" '. |= . + if .records?[]?[$field] != null then { "\($field_name_id)" : .records[][$field].Id } else empty end | if .records?[]?[$field] != null then del(.records[][$field]) else empty end' Contacts.json
I've wrestled with this quite a while, but this is as far as I'm able to manage without running into tons of syntax errors. I really appreciate any help to add an AccountID field on each object in the records array.
Here's the actual bash script where jq is being run ( relevant parts are where FIELD(S) is being used )
#! /bin/bash
# This script takes a of soql file as first and only argument
# The main purpose is to tweak the json results from an sfdx:data:tree:export so the json is compatible with sfdx:data:tree:import
# This is needed because sfdx export & import are inadequate to use whne relationships more than 2 levels deep in the export query.
# grab all unique object names within the soql file for any objects where the ID field is being SELECTed ( eg. "Account Iteration__r Profile UserRole" )
FIELDS=`grep -oe '\([A-Za-z_]\+\)\.[iI][dD]' $1 | cut -f 1 -d . - | sort -u`
#find all json files in file and rewrite the relationship FIELDS blocks into someting sfdx can import
for FIELD in $FIELDS;
do
if [[ $FIELD =~ __r ]]
then
FIELD_NAME_ID=`sed 's/__r/__c/' <<< $FIELD`
else
FIELD_NAME_ID="${FIELD}ID"
fi
JSON_FILES=`ls *.json`
#Loop all json files in direcotry
for DATA_FILE in $JSON_FILES
do
#replace any email addresses left in custom data( just in case )
#using gsed becuse Mac lacks -i flag for in-place substitution
gsed -i 's/[^@ "]*@[^@]*\.[^@ ,"]*/[email protected]/g' $DATA_FILE
# make temporary file to hold the rewritten json
TEMP_FILE="temp-${DATA_FILE}.bk"
echo $DATA_FILE $FIELD $FIELD_NAME_ID
#For custom relationship jttrs. change __r to __c to get the name of Id field, otherwise just add "ID".
jq -c --arg field $FIELD --arg field_name_id $FIELD_NAME_ID '. |= . + if .records?[]?[$field] != null then { "\($field_name_id)" : .records[][$field].Id } else empty end | if .records?[]?[$field] != null then del(.records[][$field]) else empty end' $DATA_FILE 1> ./$TEMP_FILE 2> modify-json.errors
# if TEMP_FILE is not empty, then jq revised it, so replace contents the original JSON DATA_FILE
if [[ -s ./$TEMP_FILE ]]
then
#JSON format spacing/line-breaks
jq '.' $TEMP_FILE > $DATA_FILE
fi
rm $TEMP_FILE
done
done
Upvotes: 2
Views: 1125
Reputation: 295687
Adapting peak's answer to use the dynamic field name:
jq -c --arg field "Account" \
--arg field_name_id "AccountID" '
.records |= map(.[$field].Id as $x
| del(.[$field])
| . + {($field_name_id): $x})
'
Upvotes: 2
Reputation: 116919
The key to a simple solution is |=
. Here's one using map
:
.records |= map( .Account.Id as $x
| del(.Account)
| . + {AccountID: $x} )
which can be simplified to:
.records |= map( . + {AccountID: .Account.Id}
| del(.Account) )
Either of these can easily be adapted to the case where the two field names are passed in as arguments, or if they must be inferred from the "owner" of "Id".
Upvotes: 4