Comparing JSON and Bicep for templates

This article compares Bicep syntax with JSON syntax for Azure Resource Manager templates (ARM templates). In most cases, Bicep provides syntax that is less verbose than the equivalent in JSON.

If you're familiar with using JSON to develop ARM templates, use the following examples to learn about the equivalent syntax for Bicep.

Compare complete files

The Bicep Playground lets you view Bicep and equivalent JSON side by side. You can compare the implementations of the same infrastructure.

For example, you can view the file to deploy a SQL server and database. The Bicep is about half the size of the ARM template.

Screenshot of side by side templates

Expressions

To author an expression:

func()
"[func()]"

Parameters

To declare a parameter with a default value:

param orgName string = 'Contoso'
"parameters": {
  "orgName": {
    "type": "string",
    "defaultValue": "Contoso"
  }
}

To get a parameter value, use the name you defined:

name: orgName
"name": "[parameters('orgName'))]"

Variables

To declare a variable:

var description = 'example value'
"variables": {
  "description": "example value"
},

To get a variable value, use the name you defined:

workloadSetting: description
"workloadSetting": "[variables('description'))]"

Strings

To concatenate strings:

name: '${namePrefix}-vm'
"name": "[concat(parameters('namePrefix'), '-vm')]"

Logical operators

To return the logical AND:

isMonday && isNovember
[and(parameter('isMonday'), parameter('isNovember'))]

To conditionally set a value:

isMonday ? 'valueIfTrue' : 'valueIfFalse'
[if(parameters('isMonday'), 'valueIfTrue', 'valueIfFalse')]

Deployment scope

To set the target scope of the deployment:

targetScope = 'subscription'
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#"

Resources

To declare a resource:

resource virtualMachine 'Microsoft.Compute/virtualMachines@2023-03-01' = {
  ...
}
"resources": [
  {
    "type": "Microsoft.Compute/virtualMachines",
    "apiVersion": "2020-06-01",
    ...
  }
]

To conditionally deploy a resource:

resource virtualMachine 'Microsoft.Compute/virtualMachines@2023-03-01' = if(deployVM) {
  ...
}
"resources": [
  {
    "condition": "[parameters('deployVM')]",
    "type": "Microsoft.Compute/virtualMachines",
    "apiVersion": "2023-03-01",
    ...
  }
]

To set a resource property:

sku: '2016-Datacenter'
"sku": "2016-Datacenter",

To get the resource ID of a resource in the template:

nic1.id
[resourceId('Microsoft.Network/networkInterfaces', variables('nic1Name'))]

Loops

To iterate over items in an array or count:

[for storageName in storageAccountNames: {
  ...
}]
"copy": {
  "name": "storagecopy",
  "count": "[length(parameters('storageAccountNames'))]"
},
...

Resource dependencies

For Bicep, you can set an explicit dependency but this approach isn't recommended. Instead, rely on implicit dependencies. An implicit dependency is created when one resource declaration references the identifier of another resource.

The following shows a network interface with an implicit dependency on a network security group. It references the network security group with netSecurityGroup.id.

resource netSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-11-01' = {
  ...
}

resource nic1 'Microsoft.Network/networkInterfaces@2022-11-01' = {
  name: nic1Name
  location: location
  properties: {
    ...
    networkSecurityGroup: {
      id: netSecurityGroup.id
    }
  }
}

If you must set an explicit dependence, use:

dependsOn: [ storageAccount ]
"dependsOn": ["[resourceId('Microsoft.Storage/storageAccounts', 'parameters('storageAccountName'))]"]

Reference resources

To get a property from a resource in the template:

storageAccount.properties.primaryEndpoints.blob
[reference(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))).primaryEndpoints.blob]

To get a property from an existing resource that isn't deployed in the template:

resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' existing = {
  name: storageAccountName
}

// use later in template as often as needed
storageAccount.properties.primaryEndpoints.blob
// required every time the property is needed
"[reference(resourceId('Microsoft.Storage/storageAccounts/', parameters('storageAccountName')), '2019-06-01').primaryEndpoints.blob]"

In Bicep, use the nested accessor (::) to get a property on a resource nested within a parent resource:

VNet1::Subnet1.properties.addressPrefix

For JSON, use reference function:

[reference(resourceId('Microsoft.Network/virtualNetworks/subnets', variables('subnetName'))).properties.addressPrefix]

Outputs

To output a property from a resource in the template:

output hostname string = publicIP.properties.dnsSettings.fqdn
"outputs": {
  "hostname": {
    "type": "string",
    "value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))).dnsSettings.fqdn]"
  },
}

To conditionally output a value:

output hostname string = condition ? publicIP.properties.dnsSettings.fqdn : ''
"outputs": {
  "hostname": {
    "condition": "[variables('condition')]",
    "type": "string",
    "value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', variables('publicIPAddressName'))).dnsSettings.fqdn]"
  }
}

The Bicep ternary operator is the equivalent to the if function in an ARM template JSON, not the condition property. The ternary syntax has to evaluate to one value or the other. If the condition is false in the preceding samples, Bicep outputs a hostname with an empty string, but JSON outputs no values.

Code reuse

To separate a solution into multiple files:

Next steps