Get array of object property names in ARM template - json

In an ARM template is there a way to get an array containing a JSON object's property names?
I don't see anything obvious in the documentation. The closest thing I see is length(object) to get the object's property count but I don't think I could even use a copy loop to get the property names.
The specific scenario I want to implement is deploying web appsettings with additional slot-sticky settings to a staging slot:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"webSiteName": {
"type": "string"
},
"globalAppSettings": {
"type": "object"
},
"slotName": {
"type": "string"
},
"slotAppSettings": {
"type": "object"
},
"slotStickySettings": {
"type": "array",
// but getPropertyNames(object) is not a real function :(
"defaultValue": "[getPropertyNames(parameters('slotAppSettings'))]"
}
},
"resources": [
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(parameters('webSiteName'), '/appsettings')]",
"apiVersion": "2018-02-01",
"properties": "[parameters('globalAppSettings')]"
},
{
"type": "Microsoft.Web/sites/slots/config",
"name": "[concat(parameters('webSiteName'), '/', parameters('slotName'), '/appsettings')]",
"apiVersion": "2018-02-01",
"properties": "[union(parameters('globalAppSettings'), parameters('slotAppSettings'))]"
},
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(parameters('webSiteName'), '/slotconfignames')]",
"apiVersion": "2015-08-01",
"properties": {
"appSettingNames": "[parameters('slotStickySettings')]"
}
}
]
}

There is not a straightforward way of doing this as there is no function to return the properties of an object. I was able to accomplish it by converting the the object to a string and then parsing it to find the property names. It should work as long as you don't have commas in your property values. You could probably add some checks to handle that case as well if needed.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"testSettings": {
"type": "object",
"defaultValue": {
"a": "demo value 1",
"b": "demo value 2"
}
}
},
"variables": {
"delimiters": [","],
"settingArray": "[split(replace(replace(replace(string(parameters('testSettings')), '{', ''), '}', ''), '\"', ''), variables('delimiters'))]",
"propNameArray": {
"copy": [
{
"name": "copyPropertyNames",
"count": "[length(variables('settingArray'))]",
"input": "[substring(variables('settingArray')[copyIndex('copyPropertyNames')], 0, indexOf(variables('settingArray')[copyIndex('copyPropertyNames')], ':'))]"
}
]
}
},
"resources": [],
"outputs": {
"paramOutput": {
"type": "array",
"value": "[variables('propNameArray').copyPropertyNames]"
}
}
}

Related

JSON Schema Conditionals - Different Behaviour Between IDEs

I have two private IDE plugins that are generating JSON schemas to validate things. They are comprised of a bunch of definitions that are conditionally used with pattern regexs. The problem occurs in that the schema has different behaviours in IntelliJ and VS Code.
The schema is similar to below, however reduced and redacted:
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "array",
"additionalItems": true,
"definitions": {
"meat": {
"$id": "#/definitions/meat",
"type": "object",
"properties": {
"cut": {
"$id": "#/definitions/meat/cut",
"title": "cut",
"type": "string"
},
"fillet": {
"$id": "#/definitions/meat/fillet",
"title": "fillet"
},
"bones": {
"$id": "#/definitions/meat/bones",
"title": "bones",
"type": "string"
}
},
"required": [
"cut",
"fillet"
],
"additionalProperties": true
},
"fruit": {
"$id": "#/definitions/fruit",
"type": "object",
"properties": {
"pips": {
"$id": "#/definitions/fruit/pips",
"title": "pips",
"type": "string"
},
"stone": {
"$id": "#/definitions/fruit/stone",
"title": "stone"
},
"citrus": {
"$id": "#/definitions/fruit/citrus",
"title": "citrus",
"type": "string"
}
},
"required": [
"stone",
"pips"
],
"additionalProperties": true
}
},
"items": {
"$id": "#/items",
"type": "object",
"properties": {
"name": {
"$id": "#/items/name",
"type": "string",
"title": "Name"
},
"food": {
"$id": "#/items/food",
"type": "string"
}
},
"allOf": [
{
"if": {
"properties": {
"food": {
"pattern": "meat"
}
}
},
"then": {
"properties": {
"variants": {
"$ref": "#/definitions/meat"
}
}
}
},
{
"if": {
"properties": {
"food": {
"pattern": "fruit"
}
}
},
"then": {
"properties": {
"variants": {
"$ref": "#/definitions/fruit"
}
}
}
}
],
"additionalProperties": true
}
With YAML structure:
- name: orange
food: fruit
variants:
pips: true
So with food being fruit it will match, validate and suggest for that schema, and require stone and pips.
This works in VS Code. IntelliJ however, requires stone and pips correctly, however suggests keys from meat's properties.
This made me question if I have the correct implementation here - should I be using allOf, anyOf or oneOf here? I only need to match one. I also tried adding else: false, however that didn't change anything, and I still get suggestions for meat. When originally writing it, I think I found someone saying allOf will evaluate correctly as the unmatched patterns won't stop the merging of subschemas.
What I'm trying to identify is, is this an issue with my schema implementation, or a bug in IntelliJ's parsing of conditions? Or perhaps a bug in VS Code's parsing that has let me get away with faulty schemas.

JSON Schema for child objects with different set of keys

I have JSON data of which is an array of data like
[
{
"type": "background_color",
"data": {
"backgroundColor": "F9192D"
}
},
{
"type": "banner_images",
"data": {
"images": [
{
"url": "https://example.com/abc.jpg",
"id": 3085
},
{
"url": "https://example.com/zyx.jpg",
"id": 3086
}
]
}
},
{
"type": "description_box",
"data": {
"text": "Hello 56787"
}
}
]
The data is an array of object which has two keys type and data. The type and keys of the data will be defined by the type of data it has.
Like for background_color type, the data should have backgroundColor property, while for banner_images, data should have images which is an array of other properties.
Till now, What I have done is
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"title": "category schema",
"description": "Used to validate data of category",
"examples": [],
"required": [],
"items": {
"type": "object",
"required": [
"type",
"data"
],
"properties": {
"type": {
"type": "string",
"enum": ["background_color", "banner_images", "description_box"]
},
"data": {
"type": "object" // How to define data property here for each use case
}
}
}
}
I'm not getting how to define the data property for each use case?
You can use if/then/else blocks to define conditional constraints.
The values of if and then are schemas. If the if schema is valid, then the then schema is applied, otherwise, the allOf subschema (allOf[0] in this example) would pass validation.
There are a few different ways to do this, but this is clean when you don't have any additional or special requirements. Please come back if you do =]
In this example, I've added banner_images...
You can test it working here.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"title": "category schema",
"description": "Used to validate data of category",
"items": {
"type": "object",
"required": [
"type",
"data"
],
"properties": {
"type": {
"type": "string",
"enum": [
"background_color",
"banner_images",
"description_box"
]
},
"data": {
"type": "object"
}
},
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "banner_images"
}
}
},
"then": {
"properties": {
"data": {
"required": [
"images"
],
"properties": {
"images": {
"type": "array"
}
}
}
}
}
}
]
}
}
For reference, here's the part of the JSON Schema draft-7 spec document that details the behaviour: https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.6

JSON Schema if-else condition complex scenario

{
"policyHolder": {
"fullName": "A"
},
"traveller": [
{
"fullName": "B",
"relationship": "Spouse"
},
{
"fullName": "A",
"relationship": "My Self"
}
]
}
In above json, I want to validate that
if "relationship" = "My Self" then fullName must match the fullName in policyHolder
A field relationship must exist in traveller array, else json is invalid
I have tried to create a json schema with if-else, allOf, etc. but nothing works which can do these validations but not able to.
Please help!!
Schema:
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"properties": {
"policyHolder": {
"$id": "#/properties/policyHolder",
"type": "object",
"required": [
"fullName"
],
"properties": {
"fullName": {
"$id": "#/properties/policyHolder/properties/fullName",
"type": "string",
}
}
},
"traveller": {
"$id": "#/properties/traveller",
"type": "array",
"minItems": 1,
"items": {
"$id": "#/properties/traveller/items",
"type": "object",
"properties": {
"fullName": {
"$ref": "#/properties/policyHolder/properties/fullName"
},
"relationship": {
"$id": "#/properties/traveller/items/properties/relationship",
"type": "string",
}
},
"required": [
"fullName",
"relationship"
],
}
}
}
}```
It's your first requirement that you're going to have the most trouble with. JSON Schema doesn't support validation of data against data elsewhere in the instance. It's a highly discussed topic, but nothing has been adopted yet. I suggest you verify this with a little code.
For the second, I would suggest you extract some of your subschemas into definitions rather than trying to muck about with IDs. IDs are typically more beneficial if you're referencing them from other documents or if you use short (like single-word) IDs. Defining the ID as its location in the document is redundant; most processors will handle this automatically.
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"definitions": {
"person": {
"type": "object"
"properties": {
"fullName": {"type": "string"}
},
"required": ["fullName"]
},
"relationship": { "enum": [ ... ] } // list possible relationships
},
"properties": {
"policyHolder": { "$ref": "#/definitions/person" },
"traveller": {
"type": "array",
"minItems": 1,
"items": {
"allOf": [
{ "$ref": "#/definitions/person" },
{
"properties": {
"relationship": { "$ref": "#/definitions/relationship" }
},
"required": ["relationship"]
}
]
}
}
}
}
(I extracted the relationship into its own enum definition, but this is really optional. You can leave it inline, or even an unrestricted string if you don't have a defined set of relationships.)
This can't currently be done with JSON Schema. All JSON Schema keywords can only operate on one value at a time. There's a proposal for adding a $data keyword that would enable doing this kind of validation, but I don't think it's likely to be adopted. $data would work like $ref except it references the JSON being validated rather than referencing the schema.
Here's what how you would solve your problem with $data.
{
"type": "object",
"properties": {
"policyHolder": {
"type": "object",
"properties": {
"fullName": { "type": "string" }
}
},
"traveler": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fullName": { "type": "string" },
"relationship": { "type": "string" }
},
"if": {
"properties": {
"relationship": { "const": "My Self" }
}
},
"then": {
"properties": {
"fullName": { "const": { "$data": "#/policyHolder/fullName" } }
}
}
}
}
}
}
Without $data, you will have to do this validation in code or change your data structure so that it isn't necessary.

Azure ARM copy Index not working in Json

I am trying to deploy multiple Azure VNETs using the code below,it
it gives error below
{
"error": {
"code": "InvalidTemplate",
"details": null,
"message": "Deployment template validation failed: 'The template resource '[concat(variables('namePrefix'), parameters('VNetSettings').vnets.name, [copyIndex(1)])]' at line '1' and column '923' is not valid: The language expression property 'name' has an invalid array index.. Please see https://aka.ms/arm-template-expressions for usage details.'.",
"target": null
},
"properties": null
}
This is my Code below. I want to use coyIndex to loop through multiple instances of azure virtual networks. Based on number of vnet names provided i want it get number of instances to be created.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"VNetSettings": {
"type": "object"
},
"namingSettings": {
"type": "object"
}
},
"variables": {
"namePrefix": "[concat(parameters('namingSettings').name.org,'-',parameters('namingSettings').name.accountEnv,'-',parameters('namingSettings').name.sdlcEnv,'-',parameters('namingSettings').name.region,'-',parameters('namingSettings').name.appname,'-')]"
},
"resources": [
{
"apiVersion": "2018-04-01",
"type": "Microsoft.Network/virtualNetworks",
"name": "[concat(variables('namePrefix'), parameters('VNetSettings').vnets.name, [copyIndex(1)])]",
"location": "[resourceGroup().location]",
"copy": {
"name": "vnetcopy",
"count": "[length(parameters('VNetSettings').vnets.name)]"
},
"scale": null,
"properties": {
"addressSpace": {
"addressPrefixes": [
"[parameters('VNetSettings').vnets.cidr]"
]
}
}
}
]
}
Update
Parameter fileenter code here
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters":{
"namingSettings": {
"value": {
"name": {
"org": "it",
"accountEnv": "nonprod",
"sdlcEnv": "it-test",
"region": "eastus2",
"tier": "",
"object": "",
"networkbuild": "network",
"auditbuild": "audit",
"automationbuild": "automation",
"dnsbuild": "dns",
"appname": "network"
}
}
},
"VNetSettings": {
"value": {
"vnets": [
{
"name": "vnet0",
"cidr": "10.10.10.0/24",
}
],
}
}
}
}
Your concat() input is wrong. copyIndex() should not be inside []. Try this:
"[concat(variables('namePrefix'), parameters('VNetSettings').vnets.name, copyIndex(1))]"
copyIndex() is just another function, you only wrap the string with [] once no matter how many functions are inside
you need to adjust your template to reflect the fact you are iterating over an array (right now you iterate over the name, and vnets.name is not a valid construct in the ARM Templates).
you need to do something like this:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"VNetSettings": {
"type": "object"
},
"namingSettings": {
"type": "object"
}
},
"variables": {
"namePrefix": "[concat(parameters('namingSettings').name.org,'-',parameters('namingSettings').name.accountEnv,'-',parameters('namingSettings').name.sdlcEnv,'-',parameters('namingSettings').name.region,'-',parameters('namingSettings').name.appname,'-')]"
},
"resources": [
{
"apiVersion": "2018-04-01",
"type": "Microsoft.Network/virtualNetworks",
"name": "[concat(variables('namePrefix'), parameters('VNetSettings').vnets[copyIndex()].name)]",
"location": "[resourceGroup().location]",
"copy": {
"name": "vnetcopy",
"count": "[length(parameters('VNetSettings').vnets)]"
},
"scale": null,
"properties": {
"addressSpace": {
"addressPrefixes": [
"[parameters('VNetSettings').vnets[copyIndex()].cidr]"
]
}
}
}
]
}
notice you need to use copyIndex() to get to the current vnet in your array and you need to use the .vnet to determine length

Can maxItems/minItems be used with a $ref in a JSON schema

Given a JSON schema with the following in the definitions section:
"phoneNumber": {
"type": "object",
"properties": {
"countryCode": {
"type": "number"
},
"areaCode": {
"type": "number"
},
"number": {
"type": "number"
},
"extension": {
"type": "number"
},
"service": {
"type": "string",
"enum": ["Voice", "Fax", "Data"]
},
"class": {
"type": "string",
"enum": ["Switchboard", "Direct", "PA", "Mobile"]
}
}
}
If I want to include phoneNumber elsewhere using a $ref and want the JSON to validate if it contains multiple occurrences of phoneNumber, can I use maxItems/minItems:
"person": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"phoneNumber": {
"$ref": "#/definitions/phoneNumber"
//can I use maxItems/minItems here?
}
}
}
Can I use maxItems and minItems here, or would I have to do something like this below for it to validate:
"phoneNumber": {
"allOf": { "$ref": "#/definitions/phoneNumber" },
"maxItems": 4
}
$ref must stand alone. The option you identified using allOf is the best way to do it.
Any members other than "$ref" in a JSON Reference object SHALL be ignored.
https://datatracker.ietf.org/doc/html/draft-pbryan-zyp-json-ref-03#section-3