@@ -54,7 +54,9 @@ internal static class Functions
54
54
new FunctionDescriptor ( "length" , Length ) ,
55
55
new FunctionDescriptor ( "min" , Min ) ,
56
56
new FunctionDescriptor ( "max" , Max ) ,
57
+ new FunctionDescriptor ( "objectKeys" , ObjectKeys ) ,
57
58
new FunctionDescriptor ( "range" , Range ) ,
59
+ new FunctionDescriptor ( "shallowMerge" , ShallowMerge ) ,
58
60
new FunctionDescriptor ( "skip" , Skip ) ,
59
61
new FunctionDescriptor ( "take" , Take ) ,
60
62
new FunctionDescriptor ( "tryGet" , TryGet ) ,
@@ -163,6 +165,8 @@ internal static class Functions
163
165
new FunctionDescriptor ( "reduce" , Reduce , delayBinding : true ) ,
164
166
new FunctionDescriptor ( "sort" , Sort , delayBinding : true ) ,
165
167
new FunctionDescriptor ( "toObject" , ToObject , delayBinding : true ) ,
168
+ new FunctionDescriptor ( "mapValues" , MapValues , delayBinding : true ) ,
169
+ new FunctionDescriptor ( "groupBy" , GroupBy , delayBinding : true ) ,
166
170
new FunctionDescriptor ( "lambda" , Lambda , delayBinding : true ) ,
167
171
new FunctionDescriptor ( "lambdaVariables" , LambdaVariables , delayBinding : true ) ,
168
172
@@ -722,8 +726,10 @@ internal static object TryGet(ITemplateContext context, object[] args)
722
726
/// union(arg1, arg2, arg3, ...)
723
727
/// </summary>
724
728
/// <remarks>
725
- /// Returns a single array or object with all elements from the parameters. For arrays, duplicate values are included once.
729
+ /// Returns a single array or object with all elements from the parameters.
730
+ /// For arrays, duplicate values are included once.
726
731
/// For objects, duplicate property names are only included once.
732
+ /// If there are duplicate keys, the last key wins.
727
733
/// See <seealso href="https://learn.microsoft.com/azure/azure-resource-manager/templates/template-functions-object#union"/>.
728
734
/// </remarks>
729
735
internal static object Union ( ITemplateContext context , object [ ] args )
@@ -748,11 +754,61 @@ internal static object Union(ITemplateContext context, object[] args)
748
754
749
755
// Object
750
756
if ( ExpressionHelpers . IsObject ( args [ i ] ) )
751
- return ExpressionHelpers . UnionObject ( args ) ;
757
+ return ExpressionHelpers . UnionObject ( args , deepMerge : true ) ;
752
758
}
753
759
754
760
// Handle mocks as objects if no other object or array is found.
755
- return hasMocks ? ExpressionHelpers . UnionObject ( args ) : null ;
761
+ return hasMocks ? ExpressionHelpers . UnionObject ( args , deepMerge : true ) : null ;
762
+ }
763
+
764
+ /// <summary>
765
+ /// shallowMerge(inputArray)
766
+ /// </summary>
767
+ /// <remarks>
768
+ /// Combines an array of objects, where only the top-level objects are merged.
769
+ /// This means that if the objects being merged contain nested objects, those nested object aren't deeply merged.
770
+ /// Instead, they're replaced entirely by the corresponding property from the merging object.
771
+ /// Returns a single object with all elements from the parameters.
772
+ /// If there are duplicate keys, the last key wins.
773
+ /// See <seealso href="https://learn.microsoft.com/azure/azure-resource-manager/templates/template-functions-object#shallowmerge"/>.
774
+ /// </remarks>
775
+ /// <returns>Returns an object.</returns>
776
+ internal static object ShallowMerge ( ITemplateContext context , object [ ] args )
777
+ {
778
+ if ( CountArgs ( args ) != 1 )
779
+ throw ArgumentsOutOfRange ( nameof ( ShallowMerge ) , args ) ;
780
+
781
+ if ( ! ExpressionHelpers . TryArray ( args [ 0 ] , out var entries ) )
782
+ throw ArgumentFormatInvalid ( nameof ( ShallowMerge ) ) ;
783
+
784
+ return ExpressionHelpers . UnionObject ( entries . OfType < object > ( ) . ToArray ( ) , deepMerge : false ) ;
785
+ }
786
+
787
+ /// <summary>
788
+ /// objectKeys(object)
789
+ /// </summary>
790
+ /// <remarks>
791
+ /// Returns the keys from an object, where an object is a collection of key-value pairs.
792
+ /// Elements are consistently ordered alphabetically but case-insensitive see <see href="https://github.com/Azure/bicep/issues/14057"/>.
793
+ /// See <seealso href="https://learn.microsoft.com/azure/azure-resource-manager/templates/template-functions-object#objectkeys"/>.
794
+ /// </remarks>
795
+ /// <returns>An array of keys.</returns>
796
+ internal static object ObjectKeys ( ITemplateContext context , object [ ] args )
797
+ {
798
+ if ( CountArgs ( args ) != 1 )
799
+ throw ArgumentsOutOfRange ( nameof ( ObjectKeys ) , args ) ;
800
+
801
+
802
+ if ( ! ExpressionHelpers . TryJObject ( args [ 0 ] , out var jObject ) )
803
+ throw ArgumentFormatInvalid ( nameof ( ObjectKeys ) ) ;
804
+
805
+ var result = new JArray ( ) ;
806
+
807
+ // Sorting of properties is case-insensitive.
808
+ foreach ( var item in jObject . Properties ( ) . OrderBy ( p => p . Name , StringComparer . OrdinalIgnoreCase ) )
809
+ result . Add ( item . Name ) ;
810
+
811
+ return result ;
756
812
}
757
813
758
814
#endregion Array and object
@@ -1986,6 +2042,7 @@ internal static object Reduce(ITemplateContext context, object[] args)
1986
2042
/// sort(inputArray, lambda expression)
1987
2043
/// </summary>
1988
2044
/// <remarks>
2045
+ /// Sorts an array with a custom sort function.
1989
2046
/// See <seealso href="https://learn.microsoft.com/azure/azure-resource-manager/bicep/bicep-functions-lambda#sort"/>.
1990
2047
/// </remarks>
1991
2048
internal static object Sort ( ITemplateContext context , object [ ] args )
@@ -2004,6 +2061,13 @@ internal static object Sort(ITemplateContext context, object[] args)
2004
2061
return lambda . Sort ( context , inputArray . OfType < object > ( ) . ToArray ( ) ) ;
2005
2062
}
2006
2063
2064
+ /// <summary>
2065
+ /// toObject(inputArray, lambda expression, [lambda expression])
2066
+ /// </summary>
2067
+ /// <remarks>
2068
+ /// Converts an array to an object with a custom key function and optional custom value function.
2069
+ /// See <seealso href="https://learn.microsoft.com/azure/azure-resource-manager/bicep/bicep-functions-lambda#toobject"/>.
2070
+ /// </remarks>
2007
2071
internal static object ToObject ( ITemplateContext context , object [ ] args )
2008
2072
{
2009
2073
var count = CountArgs ( args ) ;
@@ -2030,13 +2094,59 @@ internal static object ToObject(ITemplateContext context, object[] args)
2030
2094
return lambdaKeys . ToObject ( context , inputArray . OfType < object > ( ) . ToArray ( ) , lambdaValues ) ;
2031
2095
}
2032
2096
2097
+ /// <summary>
2098
+ /// groupBy(inputArray, lambda expression)
2099
+ /// </summary>
2100
+ /// <remarks>
2101
+ /// Creates an object with array values from an array, using a grouping condition.
2102
+ /// See <seealso href="https://learn.microsoft.com/azure/azure-resource-manager/bicep/bicep-functions-lambda#groupby"/>.
2103
+ /// </remarks>
2104
+ internal static object GroupBy ( ITemplateContext context , object [ ] args )
2105
+ {
2106
+ if ( CountArgs ( args ) != 2 )
2107
+ throw ArgumentsOutOfRange ( nameof ( GroupBy ) , args ) ;
2108
+
2109
+ args [ 0 ] = GetExpression ( context , args [ 0 ] ) ;
2110
+ if ( ! ExpressionHelpers . TryArray ( args [ 0 ] , out var inputArray ) )
2111
+ throw ArgumentFormatInvalid ( nameof ( GroupBy ) ) ;
2112
+
2113
+ args [ 1 ] = GetExpression ( context , args [ 1 ] ) ;
2114
+ if ( args [ 1 ] is not LambdaExpressionFn lambda )
2115
+ throw ArgumentFormatInvalid ( nameof ( GroupBy ) ) ;
2116
+
2117
+ return lambda . GroupBy ( context , inputArray . OfType < object > ( ) . ToArray ( ) ) ;
2118
+ }
2119
+
2120
+ /// <summary>
2121
+ /// mapValues(inputObject, lambda expression)
2122
+ /// </summary>
2123
+ /// <remarks>
2124
+ /// Creates an object from an input object, using a lambda expression to map values.
2125
+ /// See <seealso href="https://learn.microsoft.com/azure/azure-resource-manager/bicep/bicep-functions-lambda#mapvalues"/>.
2126
+ /// </remarks>
2127
+ internal static object MapValues ( ITemplateContext context , object [ ] args )
2128
+ {
2129
+ if ( CountArgs ( args ) != 2 )
2130
+ throw ArgumentsOutOfRange ( nameof ( MapValues ) , args ) ;
2131
+
2132
+ args [ 0 ] = GetExpression ( context , args [ 0 ] ) ;
2133
+ if ( ! ExpressionHelpers . TryJObject ( args [ 0 ] , out var inputObject ) )
2134
+ throw ArgumentFormatInvalid ( nameof ( MapValues ) ) ;
2135
+
2136
+ args [ 1 ] = GetExpression ( context , args [ 1 ] ) ;
2137
+ if ( args [ 1 ] is not LambdaExpressionFn lambda )
2138
+ throw ArgumentFormatInvalid ( nameof ( MapValues ) ) ;
2139
+
2140
+ return lambda . MapValues ( context , inputObject ) ;
2141
+ }
2142
+
2033
2143
/// <summary>
2034
2144
/// Evaluate a lambda expression.
2035
2145
/// </summary>
2036
2146
internal static object Lambda ( ITemplateContext context , object [ ] args )
2037
2147
{
2038
2148
var count = CountArgs ( args ) ;
2039
- if ( count is < 2 or > 3 )
2149
+ if ( count is < 2 or > 4 )
2040
2150
throw ArgumentsOutOfRange ( nameof ( Lambda ) , args ) ;
2041
2151
2042
2152
return new LambdaExpressionFn ( args ) ;
0 commit comments