17 Maps and Arrays

Maps and arrays are introduced as new datatypes in XDM 3.1. This section describes functions that operate on maps and arrays. It also describes functions that operate on JSON data structures, which make extensive use of maps and arrays.

17.1 Functions that Operate on Maps

The functions defined in this section use a conventional namespace prefix map, which is assumed to be bound to the namespace URI http://www.w3.org/2005/xpath-functions/map.

A map is an additional kind of item.

[Definition] A map consists of a set of entries. Each entry comprises a key which is an arbitrary atomic value, and an arbitrary sequence called the associated value.

[Definition] Within a map, no two entries have the same key. Two atomic values K1 and K2 are the same key for this purpose if the (internal) function call op:same-key($K1, $K2) returns true.

It is not necessary that all the keys in a map should be of the same type (for example, they can include a mixture of integers and strings).

As with all other values, the functions in this specification treat maps as immutable. For example, the map:remove function returns a map that differs from the supplied map by the omission (typically) of one entry, but the supplied map is not changed by the operation. Two calls on map:remove with the same arguments return maps that are indistinguishable from each other; there is no way of asking whether these are "the same map".

The function call map:get($map, $key) can be used to retrieve the value associated with a given key.

A map can also be viewed as a function from keys to associated values. To achieve this, a map is also a function item. The function corresponding to the map has the signature function($key as xs:anyAtomicValue) as item()*. Calling the function has the same effect as calling the get function: the expression $map($key) returns the same result as get($map, $key). For example, if $books-by-isbn is a map whose keys are ISBNs and whose assocated values are book elements, then the expression $books-by-isbn("0470192747") returns the book element with the given ISBN. The fact that a map is a function item allows it to be passed as an argument to higher-order functions that expect a function item as one of their arguments.

There is no operation to atomize a map or convert it to a string. The function fn:serialize can in some cases be used to produce a JSON representation of a map.

Function Meaning
op:same-key Determines whether two atomic values can coexist as separate keys within a map.
map:merge Returns a map that combines the entries from a number of existing maps.
map:size Returns the number of entries in the supplied map.
map:keys Returns a sequence containing all the keys present in a map
map:contains Tests whether a supplied map contains an entry for a given key
map:get Returns the value associated with a supplied key in a given map.
map:find Searches the supplied input sequence and any contained maps and arrays for a map entry with the supplied key, and returns the corresponding values.
map:put Returns a map containing all the contents of the supplied map, but with an additional entry, which replaces any existing entry for the same key.
map:entry Returns a map that contains a single entry (a key-value pair).
map:remove Returns a map containing all the entries from a supplied map, except those having a specified key.
map:filter Selects entries from a map, returning a new map.
map:for-each Applies a supplied function to every entry in a map, returning the concatenation of the results.
map:substitute Applies a supplied function to every entry in a map, returning a map whose entries have the same keys as the input, but (potentially) different associated values.
map:replace Returns a map based on the contents of an existing map, computing a new value to be associated with a supplied key.
map:build Returns a map that typically contains one entry for each item in a supplied input sequence.

17.1.1 op:same-key

Summary

Determines whether two atomic values can coexist as separate keys within a map.

Signature
op:same-key(
$k1 as xs:anyAtomicType,
$k2 as xs:anyAtomicType
) as xs:boolean
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The internal function op:same-key (which is not available at the user level) is used to assess whether two atomic values are considered to be duplicates when used as keys in a map. A map cannot contain two separate entries whose keys are the same as defined by this function. The function is also used when matching keys in functions such as map:get and map:remove.

The function returns true if and only if one of the following conditions is true:

  1. All of the following conditions are true:

    1. $k1 is an instance of xs:string, xs:anyURI, or xs:untypedAtomic

    2. $k2 is an instance of xs:string, xs:anyURI, or xs:untypedAtomic

    3. fn:codepoint-equal($k1, $k2)

    Note:

    Strings are compared without any dependency on collations.

  2. All of the following conditions are true:

    1. $k1 is an instance of xs:decimal, xs:double, or xs:float

    2. $k2 is an instance of xs:decimal, xs:double, or xs:float

    3. One of the following conditions is true:

      1. Both $k1 and $k2 are NaN

        Note:

        xs:double('NaN') is the same key as xs:float('NaN')

      2. Both $k1 and $k2 are positive infinity

        Note:

        xs:double('INF') is the same key as xs:float('INF')

      3. Both $k1 and $k2 are negative infinity

        Note:

        xs:double('-INF') is the same key as xs:float('-INF')

      4. $k1 and $k2 when converted to decimal numbers with no rounding or loss of precision are mathematically equal.

        Note:

        Every instance of xs:double, xs:float, and xs:decimal can be represented exactly as a decimal number provided enough digits are available both before and after the decimal point. Unlike the eq relation, which converts both operands to xs:double values, possibly losing precision in the process, this comparison is transitive.

        Note:

        Positive and negative zero are the same key.

  3. All of the following conditions are true:

    1. $k1 is an instance of xs:date, xs:time, xs:dateTime, xs:gYear, xs:gYearMonth, xs:gMonth, xs:gMonthDay, or xs:gDay

    2. $k2 is an instance of xs:date, xs:time, xs:dateTime, xs:gYear, xs:gYearMonth, xs:gMonth, xs:gMonthDay, or xs:gDay

    3. One of the following conditions is true:

      1. Both $k1 and $k2 have a timezone

      2. Neither $k1 nor $k2 has a timezone

    4. fn:deep-equal($k1, $k2)

      Note:

      The use of deep-equal rather than eq ensures that comparing values of different types yields false rather than an error.

    Note:

    Unlike the eq operator, this comparison has no dependency on the implicit timezone, which means that the question of whether or not a map contains duplicate keys is not dependent on this aspect of the dynamic context.

  4. All of the following conditions are true:

    1. $k1 is an instance of xs:boolean, xs:hexBinary, xs:base64Binary, xs:duration, xs:QName, or xs:NOTATION

    2. $k2 is an instance of xs:boolean, xs:hexBinary, xs:base64Binary, xs:duration, xs:QName, or xs:NOTATION

    3. fn:deep-equal($k1, $k2)

      Note:

      The use of deep-equal rather than eq ensures that comparing values of different types yields false rather than an error.

Notes

The rules for comparing keys in a map are chosen to ensure that the comparison is:

  • Context-free: there is no dependency on the static or dynamic context

  • Error-free: any two atomic values can be compared, and the result is either true or false, never an error

  • Transitive: if A is the same key as B, and B is the same key as C, then A is the same key as C.

As always, any algorithm that delivers the right result is acceptable. For example, when testing whether an xs:double value D is the same key as an xs:decimal value that has N significant digits, it is not necessary to know all the digits in the decimal expansion of D to establish the result: computing the first N+1 significant digits (or indeed, simply knowing that there are more than N significant digits) is sufficient.

17.1.2 map:merge

Summary

Returns a map that combines the entries from a number of existing maps.

Signatures
map:merge(
$maps as map(*)*
) as map(*)
map:merge(
$maps as map(*)*,
$options as map(*)
) as map(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function map:merge returns a map that is formed by combining the contents of the maps supplied in the $maps argument.

Informally, the supplied maps are combined as follows:

  1. There is one entry in the returned map for each distinct key present in the union of the input maps, where two keys are distinct if they are not the ·same key·.

  2. If there are duplicate keys, that is, if two or more maps contain entries having the ·same key·, then the way this is handled is controlled by the second ($options) argument.

The definitive specification is as follows.

  1. The effect of calling the single-argument function is the same as the effect of calling the two-argument function with an empty map as the value of $options.

  2. The $options argument can be used to control the way in which duplicate keys are handled. The ·option parameter conventions· apply.

  3. The entries that may appear in the $options map are as follows:

    Key Value Meaning
    duplicates Determines the policy for handling duplicate keys: specifically, the action to be taken if two maps in the input sequence $maps contain entries with key values K1 and K2 where K1 and K2 are the ·same key·.
    • Type: xs:string

    • Default: use-first

    reject An error is raised [err:FOJS0003] if duplicate keys are encountered.
    use-first If duplicate keys are present, all but the first of a set of duplicates are ignored, where the ordering is based on the order of maps in the $maps argument.
    use-last If duplicate keys are present, all but the last of a set of duplicates are ignored, where the ordering is based on the order of maps in the $maps argument.
    use-any If duplicate keys are present, all but one of a set of duplicates are ignored, and it is ·implementation-dependent· which one is retained.
    combine If duplicate keys are present, the result map includes an entry for the key whose associated value is the sequence-concatenation of all the values associated with the key, retaining order based on the order of maps in the $maps argument. The key value in the result map that corresponds to such a set of duplicates must be the ·same key· as each of the duplicates, but it is otherwise unconstrained: for example if the duplicate keys are xs:byte(1) and xs:short(1), the key in the result could legitimately be xs:long(1).

The result of the function call map:merge($MAPS, $OPTIONS) is defined to be consistent with the result of the expression:

let $FOJS0003 := QName("http://www.w3.org/2005/xqt-errors", "FOJS0003"),

$duplicates-handler := map {
  "use-first":   function($a, $b) {$a},
  "use-last":    function($a, $b) {$b},
  "combine":     function($a, $b) {$a, $b},
  "reject":      function($a, $b) {fn:error($FOJS0003)},
  "use-any": function($a, $b) {fn:random-number-generator()?permute(($a, $b))[1]}
},

$combine-maps := function($A as map(*), $B as map(*), $deduplicator as function(*)) {
    fn:fold-left(map:keys($B), $A, function($z, $k){ 
        if (map:contains($z, $k))
        then map:put($z, $k, $deduplicator($z($k), $B($k)))
        else map:put($z, $k, $B($k))
    })
}
return fn:fold-left($MAPS, map{}, 
    $combine-maps(?, ?, $duplicates-handler(($OPTIONS?duplicates, "use-first")[1]))
            
            

Note:

By way of explanation, $combine-maps is a function that combines two maps by iterating over the keys of the second map, adding each key and its corresponding value to the first map as it proceeds. The second call of fn:fold-left in the return clause then iterates over the maps supplied in the call to map:merge, accumulating a single map that absorbs successive maps in the input sequence by calling $combine-maps.

This algorithm processes the supplied maps in a defined order, but processes the keys within each map in implementation-dependent order.

The use of fn:random-number-generator represents one possible conformant implementation for "duplicates":"use-any", but it is not the only conformant implementation and is not intended to be a realistic implementation. The purpose of this option is to allow the implementation to use whatever strategy is most efficient; for example, if the input maps are processed in parallel, then specifying "duplicates":"use-any" means that the implementation does not need to keep track of the original order of the sequence of input maps.

Error Conditions

An error is raised [err:FOJS0003] if the value of $options indicates that duplicates are to be rejected, and a duplicate key is encountered.

An error is raised [err:FOJS0005] if the value of $options includes an entry whose key is defined in this specification, and whose value is not a permitted value for that key.

Notes

If the input is an empty sequence, the result is an empty map.

If the input is a sequence of length one, the result map is indistinguishable from the supplied map.

There is no requirement that the supplied input maps should have the same or compatible types. The type of a map (for example map(xs:integer, xs:string)) is descriptive of the entries it currently contains, but is not a constraint on how the map may be combined with other maps.

Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag",
     3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}

The expression map:merge(()) returns map{}. (Returns an empty map).

The expression map:merge((map:entry(0, "no"), map:entry(1, "yes"))) returns map{0:"no", 1:"yes"}. (Returns a map with two entries).

The expression map:merge(($week, map{7:"Unbekannt"})) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag", 7:"Unbekannt"}. (The value of the existing map is unchanged; the returned map contains all the entries from $week, supplemented with an additional entry.)

The expression map:merge(($week, map{6:"Sonnabend"}), map{"duplicates":"use-last"}) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Sonnabend"}. (The value of the existing map is unchanged; the returned map contains all the entries from $week, with one entry replaced by a new entry. Both input maps contain an entry with the key 6; the one used in the result is the one that comes last in the input sequence.)

The expression map:merge(($week, map{6:"Sonnabend"}), map{"duplicates":"use-first"}) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}. (The value of the existing map is unchanged; the returned map contains all the entries from $week, with one entry replaced by a new entry. Both input maps contain an entry with the key 6; the one used in the result is the one that comes first in the input sequence.)

The expression map:merge(($week, map{6:"Sonnabend"}), map{"duplicates":"combine"}) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:("Samstag", "Sonnabend")}. (The value of the existing map is unchanged; the returned map contains all the entries from $week, with one entry replaced by a new entry. Both input maps contain an entry with the key 6; the entry that appears in the result is the sequence-concatenation of the entries in the input maps, retaining order.)

17.1.3 map:size

Summary

Returns the number of entries in the supplied map.

Signature
map:size(
$map as map(*)
) as xs:integer
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function map:size takes any ·map· as its $map argument and returns the number of entries that are present in the map.

Examples

The expression map:size(map{}) returns 0.

The expression map:size(map{"true":1, "false":0}) returns 2.

17.1.4 map:keys

Summary

Returns a sequence containing all the keys present in a map

Signature
map:keys(
$map as map(*)
) as xs:anyAtomicType*
Properties

This function is ·nondeterministic-wrt-ordering·, ·context-independent·, and ·focus-independent·.

Rules

The function map:keys takes any ·map· as its $map argument and returns the keys that are present in the map as a sequence of atomic values, in ·implementation-dependent· order.

The function is non-deterministic with respect to ordering (see 1.7.4 Properties of functions). This means that two calls with the same argument are not guaranteed to produce the results in the same order.

Notes

The number of items in the result will be the same as the number of entries in the map, and the result sequence will contain no duplicate values.

Examples

The expression map:keys(map{1:"yes", 2:"no"}) returns some permutation of (1,2). (The result is in ·implementation-dependent· order.)

17.1.5 map:contains

Summary

Tests whether a supplied map contains an entry for a given key

Signature
map:contains(
$map as map(*),
$key as xs:anyAtomicType
) as xs:boolean
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function map:contains returns true if the ·map· supplied as $map contains an entry with the ·same key· as $key; otherwise it returns false.

Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag",
     3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}

The expression map:contains($week, 2) returns true().

The expression map:contains($week, 9) returns false().

The expression map:contains(map{}, "xyz") returns false().

The expression map:contains(map{"xyz":23}, "xyz") returns true().

The expression map:contains(map{"abc":23, "xyz":()}, "xyz") returns true().

17.1.6 map:get

Summary

Returns the value associated with a supplied key in a given map.

Signature
map:get(
$map as map(*),
$key as xs:anyAtomicType
) as item()*
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function map:get attempts to find an entry within the ·map· supplied as $map that has the ·same key· as $key. If there is such an entry, it returns the associated value; otherwise it returns an empty sequence.

Notes

A return value of () from map:get could indicate that the key is present in the map with an associated value of (), or it could indicate that the key is not present in the map. The two cases can be distinguished by calling map:contains.

Invoking the ·map· as a function item has the same effect as calling get: that is, when $map is a map, the expression $map($K) is equivalent to map:get($map, $K). Similarly, the expression map:get(map:get(map:get($map, 'employee'), 'name'), 'first') can be written as $map('employee')('name')('first').

Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag",
     3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}

The expression map:get($week, 4) returns "Donnerstag".

The expression map:get($week, 9) returns (). (When the key is not present, the function returns an empty sequence.)

The expression map:get(map:entry(7,()), 7) returns (). (An empty sequence as the result can also signify that the key is present and the associated value is an empty sequence.)

17.1.7 map:find

Summary

Searches the supplied input sequence and any contained maps and arrays for a map entry with the supplied key, and returns the corresponding values.

Signature
map:find(
$input as item()*,
$key as xs:anyAtomicType
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function map:find searches the sequence supplied as $input looking for map entries whose key is the ·same key· as $key. The associated value in any such map entry (each being in general a sequence) is returned as a member of the result array.

The search processes the $input sequence using the following recursively defined rules (any equivalent algorithm may be used provided it delivers the same result, respecting those rules that constrain the order of the result):

  1. To process a sequence, process each of its items in order.

  2. To process an item that is an array, process each of the array's members in order (each member is, in general, a sequence).

  3. To process an item that is a map, then for each key-value entry (K, V) in the map (in ·implementation-dependent· order) perform both of the following steps, in order:

    1. If K is the ·same key· as $key, then add V as a new member to the end of the result array.

    2. Process V (which is, in general, a sequence).

  4. To process an item that is neither a map nor an array, do nothing. (Such items are ignored).

Notes

If $input is an empty sequence, map, or array, or if the requested $key is not found, the result will be a zero-length array.

Examples
let $responses := [map{0:'no', 1:'yes'}, map{0:'non', 1:'oui'}, 
                  map{0:'nein', 1:('ja', 'doch')}]

The expression map:find($responses, 0) returns ['no', 'non', 'nein'].

The expression map:find($responses, 1) returns ['yes', 'oui', ('ja', 'doch')].

The expression map:find($responses, 2) returns [].

let $inventory := map{"name":"car", "id":"QZ123", 
      "parts": [map{"name":"engine", "id":"YW678", "parts":[]}]}

The expression map:find($inventory, "parts") returns [[map{"name":"engine", "id":"YW678", "parts":[]}], []].

17.1.8 map:put

Summary

Returns a map containing all the contents of the supplied map, but with an additional entry, which replaces any existing entry for the same key.

Signature
map:put(
$map as map(*),
$key as xs:anyAtomicType,
$value as item()*
) as map(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function map:put returns a ·map· that contains all entries from the supplied $map, with the exception of any entry whose key is the ·same key· as $key, together with a new entry whose key is $key and whose associated value is $value.

The effect of the function call map:put($MAP, $KEY, $VALUE) is equivalent to the result of the following steps:

  1. let $MAP2 := map:remove($MAP, $KEY)

    This returns a map in which all entries with the same key as $KEY have been removed.

  2. Construct and return a map containing:

    1. All the entries (key/value pairs) in $MAP2, and

    2. The entry map:entry($KEY, $VALUE)

Notes

There is no requirement that the type of $key and $value be consistent with the types of any existing keys and values in the supplied map.

Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag",
       3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}

The expression map:put($week, 6, "Sonnabend") returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Sonnabend"}.

The expression map:put($week, -1, "Unbekannt") returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag", -1:"Unbekannt"}.

17.1.9 map:entry

Summary

Returns a map that contains a single entry (a key-value pair).

Signature
map:entry(
$key as xs:anyAtomicType,
$value as item()*
) as map(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function map:entry returns a ·map· which contains a single entry. The key of the entry in the new map is $key, and its associated value is $value.

Notes

The function call map:entry(K, V) produces the same result as the expression map{K : V}.

The function map:entry is intended primarily for use in conjunction with the function map:merge. For example, a map containing seven entries may be constructed like this:

map:merge((
   map:entry("Su", "Sunday"),
   map:entry("Mo", "Monday"),
   map:entry("Tu", "Tuesday"),
   map:entry("We", "Wednesday"),
   map:entry("Th", "Thursday"),
   map:entry("Fr", "Friday"),
   map:entry("Sa", "Saturday")
   ))

The map:merge function can be used to construct a map with a variable number of entries, for example:

map:merge(for $b in //book return map:entry($b/isbn, $b))
Examples

The expression map:entry("M", "Monday") returns map{"M":"Monday"}.

17.1.10 map:remove

Summary

Returns a map containing all the entries from a supplied map, except those having a specified key.

Signature
map:remove(
$map as map(*),
$keys as xs:anyAtomicType*
) as map(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function map:remove returns a ·map· containing all the entries in $map except for any entry whose key is the ·same key· as an item in $keys.

No failure occurs if an item in $keys does not correspond to any entry in $map; that key value is simply ignored.

The effect of the function call map:remove($MAP, $KEY) can be described more formally as the result of the expression below:

map:merge (
    map:for-each (
       $MAP, -> $k, $v { 
               if (some $key in $KEY satisfies (op:same-key($k, $key)) 
               then () 
               else map:entry($k, $v)
             } ) ) 
Examples
let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag",
       3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}

The expression map:remove($week, 4) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 5:"Freitag", 6:"Samstag"}.

The expression map:remove($week, 23) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}.

The expression map:remove($week, (0, 6 to 7)) returns map{1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag"}.

The expression map:remove($week, ()) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}.

17.1.11 map:filter

Summary

Selects entries from a map, returning a new map.

Signature
map:filter(
$map as map(*),
$predicate as function(xs:anyAtomicType, item()*) as map(*)
) as item()*
Properties

This function is ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

The function map:filter takes any ·map· as its $map argument and applies the supplied function to each entry in the map; the result is a new map containing those entries for which the function returns true.

The function supplied as $predicate takes two arguments. It is called supplying the key of the map entry as the first argument, and the associated value as the second argument.

Examples

The expression map:filter(map{1:"Sunday", 2:"Monday", 3:"Tuesday", 4:"Wednesday", "5: "Thursday", 6:"Friday", 7:"Saturday"}, ->($k, $v){$k = (1, 7)}) returns map:filter(map{1:"Sunday", 7:"Saturday"}.

The expression map:filter(map{1:"Sunday", 2:"Monday", 3:"Tuesday", 4:"Wednesday", "5: "Thursday", 6:"Friday", 7:"Saturday"}, ->($k, $v){$v = ("Saturday", "Sunday")}) returns map:filter(map{1:"Sunday", 7:"Saturday"}.

History

Proposed for 4.0. Discussed 2022-09-20; decided to defer acceptance pending ideas for alignment with array:filter.

17.1.12 map:for-each

Summary

Applies a supplied function to every entry in a map, returning the concatenation of the results.

Signature
map:for-each(
$map as map(*),
$action as function(xs:anyAtomicType, item()*) as item()*
) as item()*
Properties

This function is ·nondeterministic-wrt-ordering·, ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

The function map:for-each takes any ·map· as its $map argument and applies the supplied function to each entry in the map, in ·implementation-dependent· order; the result is the sequence obtained by concatenating the results of these function calls.

The function is non-deterministic with respect to ordering (see 1.7.4 Properties of functions). This means that two calls with the same arguments are not guaranteed to process the map entries in the same order.

The function supplied as $action takes two arguments. It is called supplying the key of the map entry as the first argument, and the associated value as the second argument.

Examples

The expression map:for-each(map{1:"yes", 2:"no"}, function($k, $v){$k}) returns some permutation of (1,2). (This function call is equivalent to calling map:keys. The result is in implementation-dependent order.)

The expression distinct-values(map:for-each(map{1:"yes", 2:"no"}, function($k, $v){$v})) returns some permutation of ("yes", "no"). (This function call returns the distinct values present in the map, in implementation-dependent order.)

The expression map:merge(map:for-each(map{"a":1, "b":2}, function($k, $v){map:entry($k, $v+1)})) returns map{"a":2, "b":3}. (This function call returns a map with the same keys as the input map, with the value of each entry increased by one.)

This XQuery example converts the entries in a map to attributes on a newly constructed element node:

let $dimensions := map{'height': 3, 'width': 4, 'depth': 5};
return
  <box>{
     map:for-each($dimensions, function ($k, $v) { attribute {$k} {$v} })
  }</box>

The result is the element <box height="3" width="4" depth="5"/>.

17.1.13 map:substitute

Summary

Applies a supplied function to every entry in a map, returning a map whose entries have the same keys as the input, but (potentially) different associated values.

Signature
map:substitute(
$map as map(*),
$action as function(xs:anyAtomicType, item()*) as item()*
) as map(*)
Properties

This function is ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

The function map:substitute takes any ·map· as its $map argument and applies the supplied function to each entry in the map; the result is a map that associates the original set of key values with the results of these function calls.

The function supplied as $action takes two arguments. It is called supplying the key of the map entry as the first argument, and the associated value as the second argument.

Notes

The function call map:substitute($m, $f) returns the same result as map:merge(map:for-each($m, -> ($k, $v) { map:entry($k, $f($k, $v)) })).

Examples

The expression map:substitute(map{1:true(), 2:false()}, function($k, $v){not($v)}) returns map{1:false(), 2:true()}.

The expression map:substitute(map{1:"yes", 2:"no"}, function($k, $v){$v || ' (' || $k || ')'})) returns map{1:"yes (1)", 2:"no (2)"}.

17.1.14 map:replace

Summary

Returns a map based on the contents of an existing map, computing a new value to be associated with a supplied key.

Signature
map:replace(
$map as map(*),
$key as xs:anyAtomicType,
$action as function(item()*) as item()*
) as map(*)
Properties

This function is ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

If the supplied $map contains an existing entry for the supplied $key, then the returned map contains an entry for that $key whose value is obtained by applying the supplied $action to the existing value associated with that key.

Otherwise, the returned map contains an entry for the supplied $key whose value is obtained by applying the supplied $action to an empty sequence.

The effect of the function call map:replace($MAP, $KEY, $VALUE) is equivalent to the result of the expression:

if (map:contains($MAP, $KEY)) 
then map:put($MAP, $KEY, $ACTION(map:get($MAP, $KEY)))
else map:put($MAP, $KEY, $ACTION(())
Examples

The expression map:replace(map{1:"alpha", 2:"beta"}, 1, fn:upper-case#1) returns map{1:"ALPHA", 2:"beta"}.

The expression map:replace(map{1:"alpha", 2:"beta"}, 3, fn:upper-case#1) returns map{1:"alpha", 2:"beta" 3:""}.

The expression fold-left(("a", "b", "c", "a"), map{}, function($map, $key) {map:replace($map, $key, function($val){($val otherwise 0) + 1}}) returns map{"a":2, "b":1, "c":1}.

17.1.15 map:build

Summary

Returns a map that typically contains one entry for each item in a supplied input sequence.

Signature
map:build(
$input as item()*,
$key as function(item()) as xs:anyAtomicType? := fn:identity#1,
$value as function(item()) as item()* := fn:identity#1,
$combine as function(item()*, item()*) as item()* := fn:op(',')
) as map(*)
Properties

This function is ·deterministic·, ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

Informally, the function processes each item in $input in order. It calls the $key function on that item to obtain a key value, and the $value function to obtain an associated value. If the key is non-empty, then:

  • If the key is not already present in the target map, the processor adds a new key-value pair to the map, with that key and that value.

  • If the key is already present, the processor calls the $combine function to combine the existing value for the key with the new value, and replaces the entry with this combined value.

More formally, the result of the function is the result of the following expression:

            fold-left($input, map{}, ->($map, $next) {
               let $nextKey := $key($next)
               let $nextValue := $value($next)
               return
                  if (fn:exists($nextKey))
                  then
                     if (map:contains($map, $nextKey))
                     then map:put($map, $nextKey, $combine($map($nextKey), $nextValue))
                     else map:put($map, $nextKey, $nextValue)
                  else
                     $map
            })
         
Notes

Although defined to process the input sequence in order, this is only relevant when combining the entries for duplicate keys.

The default function for both $key and $value is the identity function. Although it is permitted to default both, this serves little purpose: usually at least one of these arguments will be supplied.

The default action for combining entries with duplicate keys is to perform a sequence-concatenation of the corresponding values, equivalent to the duplicates: combine option on map:merge. Other potentially useful functions for combining duplicates include:

  • ->($a, $b){$a} Use the first value and discard the remainder

  • ->($a, $b){$b} Use the last value and discard the remainder

  • fn:concat(?, ",", ?) Form the string-concatenation of the values, comma-separated

  • fn:op('+') Compute the sum of the values

Examples

The expression map:build((), fn:string#1) returns map{}.

The expression map:build(1 to 10, ->{. mod 3}) returns map{0: (3, 6, 9), 1: (1, 4, 7, 10), 2: (2, 5, 8)}. (Returns a map with one entry for each distinct value of . mod 3. The function to compute the value is the identity function, and duplicates are combined by sequence concatenation.)

The expression map:build(1 to 5, value := fn:format-integer(?, "w")}) returns map{1: "one", 2: "two", 3: "three", 4: "four", 5: "five"}. (Returns a map with five entries. The function to compute the key is an identity function, the function to compute the value invokes fn:format-integer.)

The expression map:build(("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"), fn:substring(?, 1, 1)}) returns map{"A": ("April", "August"), "D": ("December"), "F": ("February"), "J": ("January", "June", "July"), "M": ("March", "May"), "N": ("November"), "O": ("October"), "S": ("September")}.

The expression map:build(("apple", "apricot", "banana", "blueberry", "cherry"), fn:substring(?, 1, 1), fn:string-length#1, fn:op("+")) returns map{"a": 12, "b": 15, "c": 6}. (Constructs a map where the key is the first character of an input item, and where the corresponding value is the total string-length of the items starting with that character.)

The following expression creates a map whose keys are employee @ssn values, and whose corresponding values are the employee nodes:

map:build(//employee, ->{@ssn})

The following expression creates a map whose keys are employee @location values, and whose corresponding values represent the number of employees at each distinct location. Any employees that lack an @location attribute will be excluded from the result.

map:build(//employee, ->{@location}, ->{1}, fn:op("+"))

The following expression creates a map whose keys are employee @location values, and whose corresponding values contain the employee node for the highest-paid employee at each distinct location:

map:build(//employee, ->{@location}, 
               combine := ->($a, $b){fn:highest(($a, $b), ->{xs:decimal(@salary)}))

The following expression creates a map allowing efficient access to every element in a document by means of its fn:generate-id value:

map:build(//*, fn:generate-id#1)
History

Accepted for version 4.0 2022-10-18

17.2 Other Operations on Maps

Because a map is a function item, functions that apply to functions also apply to maps. A map is an anonymous function, so fn:function-name returns the empty sequence; fn:function-arity always returns 1.

Maps may be compared using the fn:deep-equal function.

There is no function or operator to atomize a map or convert it to a string (other than fn:serialize, which can be used to serialize some maps as JSON texts).

17.3 Functions that Operate on Arrays

An array is an additional kind of item. An array of size N is a mapping from the integers (1 to N) to a set of values, called the members of the array, each of which is an arbitrary sequence. Because an array is an item, and therefore a sequence, arrays can be nested.

The functions defined in this section use a conventional namespace prefix array, which is assumed to be bound to the namespace URI http://www.w3.org/2005/xpath-functions/array.

As with all other values, arrays are treated as immutable. For example, the array:reverse function returns an array that differs from the supplied array in the order of its members, but the supplied array is not changed by the operation. Two calls on array:reverse with the same argument will return arrays that are indistinguishable from each other; there is no way of asking whether these are "the same array". Like sequences, arrays have no identity.

An array acts as a function from integer positions to associated values, so the function call $array($index) can be used to retrieve the array member at a given position. The function corresponding to the array has the signature function($index as xs:integer) as item()*. The fact that an array is a function item allows it to be passed as an argument to higher-order functions that expect a function item as one of their arguments.

In the function definitions that follow, all the array functions are defined in terms of five primitives:

There are two operations on arrays for which the XPath language provides custom syntax:

Function Meaning
array:size Returns the number of members in the supplied array.
array:empty Returns true if the supplied array contains no members.
array:exists Returns true if the supplied array contains one or more members.
array:get Returns the value at the specified position in the supplied array (counting from 1).
array:put Returns an array containing all the members of a supplied array, except for one member which is replaced with a new value.
array:replace Returns an array containing all the members of a supplied array, except for one member which is replaced with a new value, the new value being computed from the previous value.
array:append Returns an array containing all the members of a supplied array, plus one additional member at the end.
array:slice Returns an array containing selected members of a supplied input array based on their position.
array:subarray Returns an array containing all members from a supplied array starting at a supplied position, up to a specified length.
array:remove Returns an array containing all the members of the supplied array, except for the members at specified positions.
array:insert-before Returns an array containing all the members of the supplied array, with one additional member at a specified position.
array:head Returns the first member of an array, that is $array(1).
array:foot Returns the last member of an array, that is $array(array:size($array)).
array:tail Returns an array containing all members except the first from a supplied array.
array:trunk Returns an array containing all members except the last from a supplied array.
array:reverse Returns an array containing all the members of a supplied array, but in reverse order.
array:join Concatenates the contents of several arrays into a single array.
array:for-each Returns an array whose size is the same as array:size($array), in which each member is computed by applying $function to the corresponding member of $array.
array:filter Returns an array containing those members of the $array for which $predicate returns true.
array:fold-left Evaluates the supplied function cumulatively on successive members of the supplied array.
array:fold-right Evaluates the supplied function cumulatively on successive values of the supplied array.
array:for-each-pair Returns an array obtained by evaluating the supplied function once for each pair of members at the same position in the two supplied arrays.
array:sort Returns an array containing all the members of the supplied array, sorted according to the value of a sort key supplied as a function.
array:flatten Replaces any array appearing in a supplied sequence with the members of the array, recursively.
array:from-sequence Returns an array obtained by evaluating the supplied function once for each item in the input sequence.
array:partition Partitions a sequence of items into a sequence of arrays containing the same items, starting a new partition when a supplied condition is true.
array:index-where Returns the position in an input array of members that match a supplied predicate.

17.3.1 array:size

Summary

Returns the number of members in the supplied array.

Signature
array:size(
$array as array(*)
) as xs:integer
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Informally, the function returns the number of members in the array.

More formally, the function returns the value of count(op:A2S($array)).

Notes

Note that because an array is an item, the fn:count function when applied to an array always returns 1 (one).

Examples

The expression array:size(["a", "b", "c"]) returns 3.

The expression array:size(["a", ["b", "c"]]) returns 2.

The expression array:size([ ]) returns 0.

The expression array:size([[ ]]) returns 1.

17.3.2 array:empty

Summary

Returns true if the supplied array contains no members.

Signature
array:empty(
$array as array(*)
) as xs:boolean
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function returns true if and only if $array contains no members, that is, if array:size($array) eq 0.

Notes

The test for emptiness is not the same as the test used by the xsl:on-empty instruction in XSLT. For example, an array is not considered empty by this function if it contains a single member that is itself an empty array.

Examples

The expression array:empty(["a", "b", "c"]) returns false().

The expression array:empty([]) returns true().

The expression array:empty([[]]) returns false().

The expression array:empty([()]) returns false().

History

Proposed for 4.0, see issue 229

17.3.3 array:exists

Summary

Returns true if the supplied array contains one or more members.

Signature
array:exists(
$array as array(*)
) as xs:boolean
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function returns true if and only if $array contains one or more members, that is, if array:size($array) gt 0.

Notes

The function name is chosen by analogy with fn:exists. Note that the function tests whether any array members exist, not whether the array itself exists. A function such as:

function($a as array(*)?) as xs:boolean { return array:exists($a) }

will raise a type error (rather than returning false) if the argument $a is an empty sequence, because array:exists does not allow the argument to be an empty sequence.

Examples

The expression array:exists(["a", "b", "c"]) returns true().

The expression array:exists([]) returns false().

The expression array:exists([[]]) returns true().

The expression array:exists([()]) returns true().

History

Proposed for 4.0, see issue 229

17.3.4 array:get

Summary

Returns the value at the specified position in the supplied array (counting from 1).

Signature
array:get(
$array as array(*),
$position as xs:integer
) as item()*
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Informally, the function returns the member at a specified position in the array.

More formally, the function returns the value of op:A2S($array)[$position]().

Error Conditions

A dynamic error occurs [err:FOAY0001] if $position is not in the range 1 to array:size($array) inclusive.

Examples

The expression ["a", "b", "c"] => array:get(2) returns "b".

The expression ["a", ["b", "c"]] => array:get(2) returns ["b", "c"].

17.3.5 array:put

Summary

Returns an array containing all the members of a supplied array, except for one member which is replaced with a new value.

Signature
array:put(
$array as array(*),
$position as xs:integer,
$member as item()*
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Informally, the result is an array whose size is array:size($array), in which all members in positions other than $position are the same as the members in the corresponding position of $array, and the member in position $position is $member.

More formally, the result is the value of the expression $array => array:remove($position) => array:insert-before($position, $member).

Error Conditions

A dynamic error occurs [err:FOAY0001] if $position is not in the range 1 to array:size($array) inclusive.

This error will always occur if $array is empty.

Examples

The expression array:put(["a", "b", "c"], 2, "d") returns ["a", "d", "c"].

The expression array:put(["a", "b", "c"], 2, ("d", "e")) returns ["a", ("d", "e"), "c"].

The expression array:put(["a"], 1, ["d", "e"]) returns [["d", "e"]].

17.3.6 array:replace

Summary

Returns an array containing all the members of a supplied array, except for one member which is replaced with a new value, the new value being computed from the previous value.

Signature
array:replace(
$array as array(*),
$position as xs:integer,
$action as function(item()*) as item()*
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Informally, the result is an array whose size is array:size($array), in which all members in positions other than $position are the same as the members in the corresponding position of $array, and the member in position $position is the result of applying the $action function to the original value in that position.

More formally, the result is the value of the expression $array => array:remove($position) => array:insert-before($position, $action($array($position))).

Error Conditions

A dynamic error occurs [err:FOAY0001] if $position is not in the range 1 to array:size($array) inclusive.

This error will always occur if $array is empty.

Examples

The expression array:replace([10, 11, 12], 2, ->{.+10}) returns [10, 21, 12].

The expression array:replace(["a", "b", "c"], 2, concat(?, "x")) returns ["a", "bx", "c"].

The expression array:replace([("a", "b"), ("c", "d")], 2, fn:reverse#1) returns [("a", "b"), ("d", "c")].

17.3.7 array:append

Summary

Returns an array containing all the members of a supplied array, plus one additional member at the end.

Signature
array:append(
$array as array(*),
$add as item()*
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Informally, the result is an array whose size is array:size($array) + 1, in which all members in positions 1 to array:size($array) are the same as the members in the corresponding position of $array, and the member in position array:size($array) + 1 is $add.

More formally, the result is the value of the expression (op:A2S($array), function(){$add}) => op:S2A().

Examples

The expression array:append(["a", "b", "c"], "d") returns ["a", "b", "c", "d"].

The expression array:append(["a", "b", "c"], ("d", "e")) returns ["a", "b", "c", ("d", "e")].

The expression array:append(["a", "b", "c"], ["d", "e"]) returns ["a", "b", "c", ["d", "e"]].

17.3.8 array:slice

Summary

Returns an array containing selected members of a supplied input array based on their position.

Signature
array:slice(
$input as array(*),
$start as xs:integer? := (),
$end as xs:integer? := (),
$step as xs:integer? := ()
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Returns the value of op:A2S($input) => fn:slice($start, $end, $step) => op:S2A()

Notes

The function is formally defined by converting the array to a sequence, applying fn:slice to this sequence, and then converting the resulting sequence back to an array.

Note that unlike other operations on arrays, there are no out-of-bounds errors for inappropriate values of $start, $end, or $step.

Examples
let $in := ['a', 'b', 'c', 'd', 'e']

The expression array:slice($in, start:2, end:4) returns ["b", "c", "d"].

The expression array:slice($in, start:2) returns ["b", "c", "d", "e"].

The expression array:slice($in, end:2) returns ["a", "b"].

The expression array:slice($in, start:3, end:3) returns ["c"].

The expression array:slice($in, start:4, end:3) returns ["d", "c"].

The expression array:slice($in, start:2, end:5, step:2) returns ["b", "d"].

The expression array:slice($in, start:5, end:2, step:-2) returns ["e", "c"].

The expression array:slice($in, start:2, end:5, step:-2) returns [].

The expression array:slice($in, start:5, end:2, step:2) returns [].

The expression array:slice($in) returns ["a", "b", "c", "d", "e"].

The expression array:slice($in, start:-1) returns ["e"].

The expression array:slice($in, start:-3) returns ["c", "d", "e"].

The expression array:slice($in, end:-2) returns ]"a", "b", "c", "d"].

The expression array:slice($in, start:2, end:-2) returns ["b", "c", "d"].

The expression array:slice($in, start:-2, end:2) returns ["d", "c", "b"].

The expression array:slice($in, start:-4, end:-2) returns ["b", "c", "d"].

The expression array:slice($in, start:-2, end:-4) returns ["d", "c", "b"].

The expression array:slice($in, start:-4, end:-2, step:2) returns ["b", "d"].

The expression array:slice($in, start:-2, end:-4, step:-2) returns ["d", "b"].

The expression array:slice(["a", "b", "c", "d"], 0) returns [].

17.3.9 array:subarray

Summary

Returns an array containing all members from a supplied array starting at a supplied position, up to a specified length.

Signatures
array:subarray(
$array as array(*),
$start as xs:integer
) as array(*)
array:subarray(
$array as array(*),
$start as xs:integer,
$length as xs:integer
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Except in error cases, the two-argument version of the function returns the same result as the three-argument version when called with $length equal to the value of array:size($array) - $start + 1.

Except in error cases, the result of the three-argument version of the function is the value of the expression op:A2S($array) => fn:subsequence($start, $length) => op:S2A().

Error Conditions

A dynamic error is raised [err:FOAY0001] if $start is less than one or greater than array:size($array) + 1.

For the three-argument version of the function:

  • A dynamic error is raised [err:FOAY0002] if $length is less than zero.

  • A dynamic error is raised [err:FOAY0001] if $start + $length is greater than array:size($array) + 1.

Notes

The value of $start can be equal to array:size($array) + 1 provided that $length is either equal to zero or omitted. In this case the result will be an empty array.

Examples

The expression array:subarray(["a", "b", "c", "d"], 2) returns ["b", "c", "d"].

The expression array:subarray(["a", "b", "c", "d"], 5) returns [ ].

The expression array:subarray(["a", "b", "c", "d"], 2, 0) returns [ ].

The expression array:subarray(["a", "b", "c", "d"], 2, 1) returns ["b"].

The expression array:subarray(["a", "b", "c", "d"], 2, 2) returns ["b", "c"].

The expression array:subarray(["a", "b", "c", "d"], 5, 0) returns [ ].

The expression array:subarray([ ], 1, 0) returns [ ].

17.3.10 array:remove

Summary

Returns an array containing all the members of the supplied array, except for the members at specified positions.

Signature
array:remove(
$array as array(*),
$positions as xs:integer*
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Informally, the function returns an array of size array:size($array) - fn:count(fn:distinct-values($positions)) containing all members from $array except the members whose position (counting from 1) is present in the sequence $positions. The order of the remaining members is preserved.

More formally, the result of the function, except in error cases, is given by the expression op:A2S($array)[not(position() = $positions)] => op:S2A().

Error Conditions

A dynamic error is raised [err:FOAY0001] if any integer in $positions is not in the range 1 to array:size($array) inclusive. By implication, an error occurs if $array is empty, unless $positions is also empty.

Examples

The expression array:remove(["a", "b", "c", "d"], 1) returns ["b", "c", "d"].

The expression array:remove(["a", "b", "c", "d"], 2) returns ["a", "c", "d" ].

The expression array:remove(["a"], 1) returns [ ].

The expression array:remove(["a", "b", "c", "d"], 1 to 3) returns ["d"].

The expression array:remove(["a", "b", "c", "d"], ()) returns ["a", "b", "c", "d"].

17.3.11 array:insert-before

Summary

Returns an array containing all the members of the supplied array, with one additional member at a specified position.

Signature
array:insert-before(
$array as array(*),
$position as xs:integer,
$member as item()*
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Informally, the function returns an array of size array:size($array) + 1 containing all members from $array whose position is less than $position, then a new member given by $member, and then all members from $array whose position is greater than or equal to $position. Positions are counted from 1.

More formally, except in error cases, the result is the value of the expression op:A2S($array) => fn:insert-before($position, function(){$member}) => op:S2A().

Error Conditions

A dynamic error occurs [err:FOAY0001] if $position is not in the range 1 to array:size($array) + 1 inclusive.

Notes

Setting $position to 1 has the effect of prepending the new member at the start of the array. Setting $position to the value array:size($array) + 1 delivers the same result as array:append($array, $member).

Examples

The expression array:insert-before(["a", "b", "c", "d"], 3, ("x", "y")) returns ["a", "b", ("x", "y"), "c", "d"].

The expression array:insert-before(["a", "b", "c", "d"], 5, ("x", "y")) returns ["a", "b", "c", "d", ("x", "y")].

The expression array:insert-before(["a", "b", "c", "d"], 3, ["x", "y"]) returns ["a", "b", ["x", "y"], "c", "d"].

17.3.12 array:head

Summary

Returns the first member of an array, that is $array(1).

Signature
array:head(
$array as array(*)
) as item()*
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function returns first member of $array, that is the value of array:get($array, 1).

Error Conditions

A dynamic error occurs [err:FOAY0001] if $array is empty.

Examples

The expression array:head([5, 6, 7, 8]) returns 5.

The expression array:head([["a", "b"], ["c", "d"]]) returns ["a", "b"].

The expression array:head([("a", "b"), ("c", "d")]) returns "a", "b".

17.3.13 array:foot

Summary

Returns the last member of an array, that is $array(array:size($array)).

Signature
array:foot(
$array as array(*)
) as item()*
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function returns last member of $array, that is the value of array:get($array, array:size($array)).

Error Conditions

A dynamic error occurs [err:FOAY0001] if $array is empty.

Examples

The expression array:foot([5, 6, 7, 8]) returns 8.

The expression array:foot([["a", "b"], ["c", "d"]]) returns ["c", "d"].

The expression array:foot([("a", "b"), ("c", "d")]) returns "c", "d".

History

Proposed for 4.0, see issue 97

17.3.14 array:tail

Summary

Returns an array containing all members except the first from a supplied array.

Signature
array:tail(
$array as array(*)
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function returns an array containing all members of the supplied array except the first, that is array:remove($array, 1).

Error Conditions

A dynamic error occurs [err:FOAY0001] if $array is empty.

Notes

If the supplied array contains exactly one member, the result will be an empty array.

Examples

The expression array:tail([5, 6, 7, 8]) returns [6, 7, 8].

The expression array:tail([5]) returns [ ].

17.3.15 array:trunk

Summary

Returns an array containing all members except the last from a supplied array.

Signature
array:trunk(
$array as array(*)
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function returns an array containing all members of the supplied array except the last, that is array:remove($array, array:size($array)).

Error Conditions

A dynamic error occurs [err:FOAY0001] if $array is empty.

Notes

If the supplied array contains exactly one member, the result will be an empty array.

Examples

The expression array:trunk([5, 6, 7, 8]) returns [5, 6, 7].

The expression array:trunk([5]) returns [ ].

History

Proposed for 4.0, see issue 97

17.3.16 array:reverse

Summary

Returns an array containing all the members of a supplied array, but in reverse order.

Signature
array:reverse(
$array as array(*)
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function returns the result of the expression: op:A2S($array) => fn:reverse() => op:S2A()

Examples

The expression array:reverse(["a", "b", "c", "d"]) returns ["d", "c", "b", "a"].

The expression array:reverse([("a", "b"), ("c", "d")]) returns [("c", "d"), ("a", "b")].

The expression array:reverse([(1 to 5)]) returns [(1, 2, 3, 4, 5)].

The expression array:reverse([]) returns [].

17.3.17 array:join

Summary

Concatenates the contents of several arrays into a single array.

Signature
array:join(
$arrays as array(*)*
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Informally, the function concatenates the members of several arrays into a single array.

More formally, the function returns the result of ($arrays ! op:A2S(.)) => op:S2A().

Examples

The expression array:join(()) returns [ ].

The expression array:join([1, 2, 3]) returns [1, 2, 3].

The expression array:join((["a", "b"], ["c", "d"])) returns ["a", "b", "c", "d"].

The expression array:join((["a", "b"], ["c", "d"], [ ])) returns ["a", "b", "c", "d"].

The expression array:join((["a", "b"], ["c", "d"], [["e", "f"]])) returns ["a", "b", "c", "d", ["e", "f"]].

17.3.18 array:for-each

Summary

Returns an array whose size is the same as array:size($array), in which each member is computed by applying $function to the corresponding member of $array.

Signature
array:for-each(
$array as array(*),
$action as function(item()*) as item()*
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

Informally, the function returns an array whose members are obtained by applying the supplied $function to each member of the input array in turn.

More formally, the function returns the result of the expression (for $member in op:A2S($array) return function(){$action($member())) => op:S2A().

Examples

The expression array:for-each(["A", "B", 1, 2], function($z) {$z instance of xs:integer}) returns [false(), false(), true(), true()].

The expression array:for-each(["the cat", "sat", "on the mat"], fn:tokenize#1) returns [("the", "cat"), "sat", ("on", "the", "mat")].

The expression array:for-each([["the", "cat"], ["sat"], ["on", "the", "mat"]], array:flatten#1) returns [("the", "cat"), "sat", ("on", "the", "mat")].

The expression array:for-each([["the", "cat"], ["sat"], ["on", "the", "mat"]], ->($m){->{$m}}) returns [("the", "cat"), "sat", ("on", "the", "mat")].

The expression array:for-each($A, ->($m){->{$m}}) returns a sequence of zero-arity functions, one for each item in the array, where the zero-arity function encapsulates the value of one array member. This construct can be useful as a bridge from arrays to sequences. For example, a function to reverse the order of members in an array can be implemented as $A => for-each(->$m{->{$m}}) => reverse()

17.3.19 array:filter

Summary

Returns an array containing those members of the $array for which $predicate returns true.

Signature
array:filter(
$array as array(*),
$predicate as function(item()*) as xs:boolean
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

Informally, the function returns an array containing those members of the input array that satisfy the supplied predicate.

More formally, the function returns the result of the expression op:A2S($array) => fn:filter(function($m){$predicate($m())}) => op:S2A().

Error Conditions

As a consequence of the function signature and the function calling rules, a type error occurs if the supplied function $function returns anything other than a single xs:boolean item; there is no conversion to an effective boolean value.

Examples

The expression array:filter(["A", "B", 1, 2], function($x) {$x instance of xs:integer}) returns [1, 2].

The expression array:filter(["the cat", "sat", "on the mat"], function($s){fn:count(fn:tokenize($s)) gt 1}) returns ["the cat", "on the mat"].

The expression array:filter(["A", "B", "", 0, 1], boolean#1) returns ["A", "B", 1].

17.3.20 array:fold-left

Summary

Evaluates the supplied function cumulatively on successive members of the supplied array.

Signature
array:fold-left(
$array as array(*),
$zero as item()*,
$action as function(item()*, item()*) as item()*
) as item()*
Properties

This function is ·deterministic·, ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

The result of the function is the value of the expression op:A2S($array) => fn:fold-left($zero, function($a, $b){$action($a, $b()})

Notes

If the supplied array is empty, the function returns $zero.

If the supplied array contains a single member $m, the function returns $zero => $action($m).

If the supplied array contains two members $m and $n, the function returns $zero => $action($m) => $action($n); and similarly for an input array with more than two members.

Examples

The expression array:fold-left([true(), true(), false()], true(), function($x, $y){$x and $y}) returns false(). (Returns true if every member of the input array has an effective boolean value of true().)

The expression array:fold-left([true(), true(), false()], false(), function($x, $y){$x or $y}) returns true(). (Returns true if at least one member of the input array has an effective boolean value of true().)

The expression array:fold-left([1,2,3], [], function($x, $y){[$x, $y]}) returns [[[[], 1], 2], 3].

17.3.21 array:fold-right

Summary

Evaluates the supplied function cumulatively on successive values of the supplied array.

Signature
array:fold-right(
$array as array(*),
$zero as item()*,
$action as function(item()*, item()*) as item()*
) as item()*
Properties

This function is ·deterministic·, ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

The result of the function is the value of the expression op:A2S($array) => fn:fold-right($zero, function($a, $b){$action($a, $b()})

Notes

If the supplied array is empty, the function returns $zero.

If the supplied array contains a single member $m, the function returns $action($m, $zero).

If the supplied array contains two members $m and $n, the function returns $action($m, $action($n, $zero)); and similarly for an input array with more than two members.

Examples

The expression array:fold-right([true(), true(), false()], true(), function($x, $y){$x and $y}) returns false(). (Returns true if every member of the input array has an effective boolean value of true().)

The expression array:fold-right([true(), true(), false()], false(), function($x, $y){$x or $y}) returns true(). (Returns true if at least one member of the input array has an effective boolean value of true().)

The expression array:fold-right([1,2,3], [], function($x, $y){[$x, $y]}) returns [1, [2, [3, []]]].

17.3.22 array:for-each-pair

Summary

Returns an array obtained by evaluating the supplied function once for each pair of members at the same position in the two supplied arrays.

Signature
array:for-each-pair(
$array1 as array(*),
$array2 as array(*),
$action as function(item()*, item()*) as item()*
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

The function returns the result of the expression fn:for-each-pair(op:A2S($array1), op:A2S($array2), function($m, $n) {function(){$action($m(), $n()}}) => op:S2A().

Notes

If the arrays have different size, excess members in the longer array are ignored.

Examples

The expression array:for-each-pair(["A", "B", "C"], [1, 2, 3], function($x, $y) { array {$x, $y}}) returns [["A", 1], ["B", 2], ["C", 3]].

The expression let $A := ["A", "B", "C", "D"] return array:for-each-pair($A, array:tail($A), concat#2) returns ["AB", "BC", "CD"].

17.3.23 array:sort

Summary

Returns an array containing all the members of the supplied array, sorted according to the value of a sort key supplied as a function.

Signatures
array:sort(
$array as array(*)
) as array(*)
array:sort(
$array as array(*),
$collation as xs:string?
) as array(*)
array:sort(
$array as array(*),
$collation as xs:string?,
$key as function(item()*) as xs:anyAtomicType*
) as array(*)
Properties

The one-argument form of this function is ·deterministic·, ·context-dependent·, and ·focus-independent·. It depends on collations.

The two-argument form of this function is ·deterministic·, ·context-dependent·, and ·focus-independent·. It depends on collations.

The three-argument form of this function is ·deterministic·, ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

Calling the single-argument version of the function is equivalent to calling the two-argument form with default-collation() as the second argument: that is, it sorts the members of an array according to the typed value of the items, using the default collation to compare strings.

Calling the two-argument version of the function is equivalent to calling the three-argument form with fn:data#1 as the third argument: that is, it sorts the members of an array according to the typed value of the items, using a specified collation to compare strings.

In the case of both array:sort#2 and array:sort#3, supplying an empty sequence as the second argument is equivalent to supplying fn:default-collation(). For more information on collations see 5.3.5 Choosing a collation.

The result of array:sort#3 is the value of the expression op:A2S($array) => fn:sort($collation, function($x){$key($x())}) => op:S2A()

Error Conditions

If the set of computed sort keys contains values that are not comparable using the le operator then the sort operation will fail with a dynamic error.

Examples

The expression array:sort([1, 4, 6, 5, 3]) returns [1, 3, 4, 5, 6].

The expression array:sort([1, -2, 5, 10, -10, 10, 8], (), fn:abs#1) returns [1, -2, 5, 8, 10, -10, 10].

The expression array:sort([(1,0), (1,1), (0,1), (0,0)]) returns [(0,0), (0,1), (1,0), (1,1)].

To sort an array of strings $in using Swedish collation:

let $SWEDISH := "http://www.w3.org/2013/collation/UCA?lang=se"
return array:sort($in, $SWEDISH)
            

To sort an array of maps representing employees, using last name as the major sort key and first name as the minor sort key, with the default collation:

array:sort($employees, (), function($emp) {$emp?name?last, $emp?name?first})

17.3.24 array:flatten

Summary

Replaces any array appearing in a supplied sequence with the members of the array, recursively.

Signature
array:flatten(
$input as item()*
) as item()*
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The function processes the items in the supplied sequence $input as follows:

  • An item that is an array is replaced by its members, retaining order.

  • Any other item is retained unchanged.

The process is then repeated so long as the sequence contains an array among its items.

The function is equivalent to the following XQuery implementation (assuming static typing is not in force):

            declare function flatten ($S as item()*) {
    for $s in $S return (
      typeswitch($s)
        case $a as array(*) return flatten($a?*)
        default return $s
)}
         
Notes

The argument to the function will often be a single array item, but this is not essential.

Unlike atomization, this function retains any nodes contained in the array.

Examples

The expression array:flatten([1, 4, 6, 5, 3]) returns (1, 4, 6, 5, 3).

The expression array:flatten(([1, 2, 5], [[10, 11], 12], [], 13)) returns (1, 2, 5, 10, 11, 12, 13).

The expression array:flatten([(1,0), (1,1), (0,1), (0,0)]) returns (1, 0, 1, 1, 0, 1, 0, 0).

17.3.25 array:from-sequence

Summary

Returns an array obtained by evaluating the supplied function once for each item in the input sequence.

Signatures
array:from-sequence(
$input as item()*
) as array(*)
array:from-sequence(
$input as item()*,
$action as function(item()) as item()*
) as array(*)
Properties

This function is ·deterministic·, ·context-independent·, ·focus-independent·, and ·higher-order·.

Rules

If the function is called with one argument, the effect is the same as calling the two-argument function with fn:identity#1 as the second argument.

Informally, array:from-sequence#2 applies the supplied function to each item in the input sequence, and the resulting sequence becomes one member of the returned array.

More formally, array:from-sequence#2 returns the result of the expression:

            fn:for-each($input, function($x)(function(){$action($x)})) => op:S2A()
         
Notes

The single-argument function array:from-sequence($input) is equivalent to the XPath expression array{$input}, but it is useful to have this available as a function.

The two-argument form facilitates the construction of arrays whose members are arbitrary sequences.

Examples

The expression array:from-sequence(1 to 5) returns [1, 2, 3, 4, 5].

The expression array:from-sequence(1 to 5, ->{2*.}) returns [2, 4, 6, 8, 10].

The expression array:from-sequence(1 to 5, ->{1 to .}) returns [1, (1,2), (1,2,3), (1,2,3,4), (1,2,3,4,5)].

The expression array:from-sequence(("red", "green", "blue"), fn:characters#1) returns [("r", "e", "d"), ("g", "r", "e", "e", "n"), ("b", "l", "u", "e)].

The expression array:from-sequence(1 to 5, ->{array{1 to .}}) returns [[1], [1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5]].

The expression array:from-sequence(1 to 20, ->{array{1 to .}}) returns [[1], [1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5]].

17.3.26 array:partition

Summary

Partitions a sequence of items into a sequence of arrays containing the same items, starting a new partition when a supplied condition is true.

Signature
array:partition(
$input as item()*,
$break-when as function(item()*, item()) as xs:boolean
) as array(*)+
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

Informally, the function processes the items in the input sequence in order, and for each item it calls the supplied $break-when function with two arguments: the contents of the current partition (initially an empty sequence), and the current item in the input sequence. If the $break-when function returns true, the current partition is added to the result and a new current partition is created, initially containing the current item. If the $break-when function returns false, the current item is added to the current partition.

More formally, the function returns the result of the expression:

fn:fold-left($input, (), function($a, $b) {
   if (empty($a) or $break-when(array:slice($a, start:-1)?*, $b))
   then ($a, [$b])
   else array:replace($a, array:size($a), function($m){$m, $b})
}   
         
Notes

The function enables a variety of positional grouping problems to be solved. For example:

  • array:partition($input, function($a, $b){count($a) eq 3} partitions a sequence into fixed size groups of length 3.

  • array:partition($input, function($a, $b){boolean($b/self::h1)} starts a new group whenever an h1 element is encountered.

  • array:partition($input, function($a, $b){$b lt $a[last()]} starts a new group whenever an item is encountered whose value is less than the value of the previous item.

Examples

The expression array:partition(("Anita", "Anne", "Barbara", "Catherine", "Christine"), ->($x, $y){fn:substring($x[last()],1,1) ne fn:substring($y,1,1)}) returns (["Anita", "Anne"], ["Barbara"], ["Catherine", "Christine"]).

The expression array:partition((1, 2, 3, 4, 5, 6), function($a, $b){count($a) eq 2}) returns ([1, 2], [3, 4], [5, 6]).

The expression array:partition((1, 4, 6, 3, 1, 1), function($a, $b){sum($a) ge 5}) returns ([1, 4], [6], [3, 1, 1]).

The expression array:partition(tokenize("In the beginning was the word"), function($a, $b){sum(($a,$b)!string-length() gt 10)} returns (["In", "the"], ["beginning"], ["was", "the", "word"]).

The expression array:partition((1, 2, 3, 6, 7, 9, 10), function($a, $b){$a ne $b[last()]+1}) returns ([1, 2, 3], [6, 7], [9, 10]).

17.3.27 array:index-where

Summary

Returns the position in an input array of members that match a supplied predicate.

Signature
array:index-where(
$array as array(*),
$predicate as function(item()*) as xs:boolean
) as xs:integer*
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The result of the function is a sequence of integers, in monotonic ascending order, representing the 1-based positions in the input array of those members for which the supplied predicate function returns true.

More formally, the function returns the result of the expression:

fn:index-of(array:for-each($input, $predicate)?*, true())
Examples

The expression array:index-where([], fn:boolean#1) returns ().

The expression array:index-where([0, (), 4, 9], fn:boolean#1) returns (3, 4).

The expression array:index-where(array{1 to 10}, ->{. mod 2 = 0})) returns (2, 4, 6, 8, 10).

The expression array:index-where(["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], fn:contains(?, "r")) returns (1, 2, 3, 4, 9, 10, 11, 12).

The expression array:index-where([(1, 2, 3), (4, 5, 6), (7, 8)], ->($m){count($m) = 3}) returns (1, 2).

History

Approved 2022-12-13 for inclusion in 4.0 - issue #114

17.4 Conversion to and from JSON

JSON is a popular format for exchange of structured data on the web: it is specified in [RFC 7159]. This section describes facilities allowing JSON data to be converted to and from XDM values.

This specification describes two ways of representing JSON data losslessly using XDM constructs. The first method uses XDM maps to represent JSON objects, and XDM arrays to represent JSON arrays. The second method represents all JSON constructs using XDM element and attribute nodes.

17.4.1 Representing JSON using maps and arrays

This section defines a mapping from JSON data to XDM maps and arrays. Two functions are available to support this mapping: fn:parse-json and fn:serialize (with options selecting JSON as the output method). The fn:parse-json function will accept any JSON text as input, and converts it to XDM data values. The fn:serialize function (with JSON as the output method) will accept any XDM value produced using fn:parse-json and convert it back to the original JSON text (subject to insignificant variations such as reordering the properties in a JSON object).

Note:

The conversion is lossless if recommended JSON good practice is followed. Information may however be lost if (a) JSON numbers are not exactly representable as double-precision floating point, or (b) duplicate key values appear within a JSON object.

The representation of JSON data produced by the fn:parse-json function has been chosen with ease of manipulation as a design aim. For example, a simple JSON object such as {"Sun":1, "Mon":2, "Tue":3, ...} produces a simple map, so if the result of parsing is held in $weekdays, the number for a given weekday can be extracted using an expression such as $weekdays?Tue. Similarly, a simple array such as ["Sun", "Mon", "Tue", ...] produces an array that can be addressed as, for example, $weekdays(3). A more deeply nested structure can be addressed in a similar way: for example if the JSON text is an array of person objects, each of which has a property named "phones" which is an array of strings containing phone numbers, then the first phone number of each person in the data can be addressed as $data?phones(1).

17.4.2 XML Representation of JSON

This section defines a mapping from JSON data to XML (specifically, to XDM element and attribute nodes). A function fn:json-to-xml is provided to take a JSON string as input and convert it to the XML representation, and a second function fn:xml-to-json performs the reverse operation.

The XML representation is designed to be capable of representing any valid JSON text including one that uses characters which are not valid in XML. The transformation is normally lossless: that is, distinct JSON texts convert to distinct XML representations. When converting JSON to XML, options are provided to reject unsupported characters, to replace them with a substitute character, or to leave them in backslash-escaped form.

Note:

The conversion is lossless if recommended JSON good practice is followed. Information may however be lost if (a) JSON numbers are not exactly representable as double-precision floating point, or (b) duplicate key values appear within a JSON object.

The following example demonstrates the correspondence of a JSON text and the corresponding XML representation.

Example: A JSON Text and its XML Representation

Consider the following JSON text:

 {
    "desc"    : "Distances between several cities, in kilometers.",
    "updated" : "2014-02-04T18:50:45",
    "uptodate": true,
    "author"  : null,
    "cities"  : {
        "Brussels": [
                      {"to": "London",    "distance": 322},
                      {"to": "Paris",     "distance": 265},
                      {"to": "Amsterdam", "distance": 173}
                    ],
        "London": [
                      {"to": "Brussels",  "distance": 322},
                      {"to": "Paris",     "distance": 344},
                      {"to": "Amsterdam", "distance": 358}
                  ],
        "Paris": [
                      {"to": "Brussels",  "distance": 265},
                      {"to": "London",    "distance": 344},
                      {"to": "Amsterdam", "distance": 431}
                 ],
        "Amsterdam": [
                      {"to": "Brussels",  "distance": 173},
                      {"to": "London",    "distance": 358},
                      {"to": "Paris",     "distance": 431}
                     ]
     }
}
                  

The XML representation of this text is as follows. Whitespace is included in the XML representation for purposes of illustration, but it will not necessarily be present in the output of the json-to-xml function.

  <map xmlns="http://www.w3.org/2005/xpath-functions">
    <string key='desc'>Distances between several cities, in kilometers.</string>
    <string key='updated'>2014-02-04T18:50:45</string>
    <boolean key="uptodate">true</boolean>
    <null key="author"/>
    <map key='cities'>
      <array key="Brussels">
        <map>
            <string key="to">London</string>
            <number key="distance">322</number>
        </map> 
        <map>
            <string key="to">Paris</string>
            <number key="distance">265</number>
        </map> 
        <map>
            <string key="to">Amsterdam</string>
            <number key="distance">173</number>
        </map> 
      </array>
      <array key="London">
        <map>
            <string key="to">Brussels</string>
            <number key="distance">322</number>
        </map> 
        <map>
            <string key="to">Paris</string>
            <number key="distance">344</number>
        </map> 
        <map>
            <string key="to">Amsterdam</string>
            <number key="distance">358</number>
        </map> 
      </array>
      <array key="Paris">
        <map>
            <string key="to">Brussels</string>
            <number key="distance">265</number>
        </map> 
        <map>
            <string key="to">London</string>
            <number key="distance">344</number>
        </map> 
        <map>
            <string key="to">Amsterdam</string>
            <number key="distance">431</number>
        </map>  
      </array>
      <array key="Amsterdam">
        <map>
            <string key="to">Brussels</string>
            <number key="distance">173</number>
        </map> 
        <map>
            <string key="to">London</string>
            <number key="distance">358</number>
        </map> 
        <map>
            <string key="to">Paris</string>
            <number key="distance">431</number>
        </map>
      </array>
    </map>  
  </map>

An XSD 1.0 schema for the XML representation is provided in C.2 Schema for the result of fn:json-to-xml. It is not necessary to import this schema into the static context unless the stylesheet or query makes explicit reference to the components defined in the schema. If the stylesheet or query does import a schema for the namespace http://www.w3.org/2005/xpath-functions, then:

  1. Unless the host language specifies otherwise, the processor (if it is schema-aware) must recognize an import declaration for this namespace, whether or not a schema location is supplied.

  2. If a schema location is provided, then the schema document at that location must be equivalent to the schema document at C.2 Schema for the result of fn:json-to-xml; the effect if it is not equivalent is ·implementation-dependent·

The rules governing the mapping from JSON to XML are as follows. In these rules, the phrase "an element named N" is to be interpreted as meaning "an element node whose local name is N and whose namespace URI is http://www.w3.org/2005/xpath-functions".

  1. The JSON value null is represented by an element named null, with empty content.

  2. The JSON values true and false are represented by an element named boolean, with content conforming to the type xs:boolean. When the element is created by the fn:json-to-xml function, the string value of the element will be true or false. The fn:xml-to-json function also recognizes other strings that validate as xs:boolean, for example 1 and 0. Leading and trailing whitespace is accepted.

  3. A JSON number is represented by an element named number, with content conforming to the type xs:double, with the additional restriction that the value must not be positive or negative infinity, nor NaN. The fn:json-to-xml function creates an element whose string value is lexically the same as the JSON representation of the number. The fn:xml-to-json function generates a JSON representation that is the result of casting the (typed or untyped) value of the node to xs:double and then casting the result to xs:string. Leading and trailing whitespace is accepted. Since JSON does not impose limits on the range or precision of numbers, these rules mean that conversion from JSON to XML will always succeed, and will retain full precision in the lexical representation unless the data model implementation is one that reconstructs the string value from the typed value. In the reverse direction, conversion from XML to JSON may fail if the value is infinity or NaN, or if the string value is such that casting to xs:double produces positive or negative infinity.

  4. A JSON string is represented by an element named string, with content conforming to the type xs:string. The string element has two alternative representations: escaped form, and unescaped form.

  5. A JSON array is represented by an element named array. The content is a sequence of child elements representing the members of the array in order, each such element being the representation of the array member obtained by applying these rules recursively.

  6. A JSON object is represented by an element named map. The content is a sequence of child elements each of which represents one of the name/value pairs in the object. The representation of the name/value pair N:V is obtained by taking the element that represents the value V (by applying these rules recursively) and adding an attribute with name key (in no namespace), whose value is N as an instance of xs:string. The functions fn:json-to-xml and fn:xml-to-json both retain the order of entries, subject to rules about how duplicate keys are handled. The key may be represented in escaped or unescaped form.

The attribute escaped="true" may be specified on a string element to indicate that the string value contains backslash-escaped characters that are to be interpreted according to the JSON rules. The attribute escaped-key="true" may be specified on any element with a key attribute to indicate that the key contains backslash-escaped characters that are to be interpreted according to the JSON rules. Both attributes have the default value false, signifying that the relevant value is in unescaped form. In unescaped form, the backslash character has no special significance (it represents itself).

The JSON grammar for number is a subset of the lexical space of the XSD type xs:double. The mapping from JSON number values to xs:double values is defined by the XPath rules for casting from xs:string to xs:double. Note that these rules will never generate an error for out-of-range values; instead very large or very small values will be converted to +INF or -INF. Since JSON does not impose limits on the range or precision of numbers, the conversion is not guaranteed to retain full precision.

Although the order of entries in a JSON object is generally considered to have no significance, the functions json-to-xml and json-to-xml both retain order.

The XDM representation of a JSON value may either be untyped (all elements annotated as xs:untyped, attributes as xs:untypedAtomic), or it may be typed. If it is typed, then it must have the type annotations obtained by validating the untyped representation against the schema given in C.2 Schema for the result of fn:json-to-xml. If it is untyped, then it must be an XDM instance such that validation against this schema would succeed; with the proviso that all attributes other than those in no namespace or in namespace http://www.w3.org/2005/xpath-functions are ignored, including attributes such as xsi:type and xsi:nil that would normally influence the process of schema validation.

The namespace prefix associated with the namespace http://www.w3.org/2005/xpath-functions (if any) is immaterial. The effect of the fn:xml-to-json function does not depend on the choice of prefix, and the prefix (if any) generated by the fn:json-to-xml function is ·implementation-dependent·.

17.5 Functions on JSON Data

The functions listed parse or serialize JSON data.

Function Meaning
fn:parse-json Parses a string supplied in the form of a JSON text, returning the results typically in the form of a map or array.
fn:json-doc Reads an external resource containing JSON, and returns the result of parsing the resource as JSON.
fn:json-to-xml Parses a string supplied in the form of a JSON text, returning the results in the form of an XML document node.
fn:xml-to-json Converts an XML tree, whose format corresponds to the XML representation of JSON defined in this specification, into a string conforming to the JSON grammar.
fn:json Creates a JSON representation of an arbitrary XDM value.

Note also that the function fn:serialize has an option to act as the inverse function to fn:parse-json.

17.5.1 fn:parse-json

Summary

Parses a string supplied in the form of a JSON text, returning the results typically in the form of a map or array.

Signatures
fn:parse-json(
$json as xs:string?
) as item()?
fn:parse-json(
$json as xs:string?,
$options as map(*)
) as item()?
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The effect of the one-argument form of this function is the same as calling the two-argument form with an empty map as the value of the $options argument.

The first argument is a JSON text as defined in [RFC 7159], in the form of a string. The function parses this string to return an XDM value.

If $json is the empty sequence, the function returns the empty sequence.

Note:

The result will also be an empty sequence if $json is the string "null".

The $options argument can be used to control the way in which the parsing takes place. The ·option parameter conventions· apply.

The entries that may appear in the $options map are as follows:

Key Value Meaning
liberal Determines whether deviations from the syntax of RFC7159 are permitted.
  • Type: xs:boolean

  • Default: false

false The input must consist of an optional byte order mark (which is ignored) followed by a string that conforms to the grammar of JSON-text in [RFC 7159]. An error must be raised [err:FOJS0001] if the input does not conform to the grammar.
true The input may contain deviations from the grammar of [RFC 7159], which are handled in an ·implementation-defined· way. (Note: some popular extensions include allowing quotes on keys to be omitted, allowing a comma to appear after the last item in an array, allowing leading zeroes in numbers, and allowing control characters such as tab and newline to be present in unescaped form.) Since the extensions accepted are implementation-defined, an error may be raised [err:FOJS0001] if the input does not conform to the grammar.
duplicates Determines the policy for handling duplicate keys in a JSON object. To determine whether keys are duplicates, they are compared using the Unicode codepoint collation, after expanding escape sequences, unless the escape option is set to true, in which case keys are compared in escaped form.
  • Type: xs:string

  • Default: use-first

reject An error is raised [err:FOJS0003] if duplicate keys are encountered.
use-first If duplicate keys are present in a JSON object, all but the first of a set of duplicates are ignored.
use-last If duplicate keys are present in a JSON object, all but the last of a set of duplicates are ignored.
escape Determines whether special characters are represented in the XDM output in backslash-escaped form.
  • Type: xs:boolean

  • Default: true

false All characters in the input that are valid in the version of XML supported by the implementation, whether or not they are represented in the input by means of an escape sequence, are represented as unescaped characters in the result. Any characters or codepoints that are not valid XML characters (for example, unpaired surrogates) are passed to the fallback function as described below; in the absence of a fallback function, they are replaced by the Unicode REPLACEMENT CHARACTER (xFFFD).
true JSON escape sequences are used in the result to represent special characters in the JSON input, as defined below, whether or not they were represented using JSON escape sequences in the input. The characters that are considered "special" for this purpose are:
  • all codepoints in the range x00 to x1F or x7F to x9F;

  • all codepoints that do not represent characters that are valid in the version of XML supported by the processor, including codepoints representing unpaired surrogates;

  • the backslash character itself (x5C).

Such characters are represented using a two-character escape sequence where available (for example, \t), or a six-character escape sequence otherwise (for example \uDEAD). Characters other than these are not escaped in the result, even if they were escaped in the input.
fallback Provides a function which is called when the input contains an escape sequence that represents a character that is not valid in the version of XML supported by the implementation. It is an error to supply the fallback option if the escape option is present with the value true.
  • Type: function(xs:string) as xs:string

  • Default: The default is effectively the function function($s){"&#xFFFD;"}: that is, a function that replaces the escape sequence with the Unicode REPLACEMENT CHARACTER.

User-supplied function The function is called when the JSON input contains a special character (as defined under the escape option) that is valid according to the JSON grammar, whether the special character is represented in the input directly or as an escape sequence. The function is called once for any surrogate that is not properly paired with another surrogate. The string supplied as the argument will always be a two- or six- character escape sequence, starting with a backslash, that conforms to the rules in the JSON grammar (as extended by the implementation if liberal:true() is specified): for example \b or \uFFFF or \uDEAD. The function is not called for an escape sequence that is invalid against the grammar (for example \x0A). The function returns a string which is inserted into the result in place of the invalid character. The function also has the option of raising a dynamic error by calling fn:error.
number-parser Determines how numeric values should be processed.
  • Type: (function(xs:string) as xs:anyAtomicType)?

  • Default: ()

User-supplied function The supplied function is called to process the string value of any JSON number in the input. By default, numbers are processed by converting to xs:double using the XPath casting rules. Supplying the value xs:decimal#1 will instead convert to xs:decimal (which potentially retains more precision, but disallows exponential notation), while supplying a function that casts to union(xs:decimal, xs:double) will treat the value as xs:decimal if there is no exponent, or as xs:double otherwise. Supplying the value fn:identity#1 causes the value to be retained unchanged as an xs:string. Before calling the supplied number-parser, the value is first checked to ensure that it conforms to the JSON grammar (for example, a leading plus sign and redundant leading zeroes are not allowed); these checks are disabled if the liberal option is set to true.
() If no function is supplied, numbers are processed by casting the supplied value to xs:double.

The various structures that can occur in JSON are transformed recursively to XDM values as follows:

  1. A JSON object is converted to a map. The entries in the map correspond to the key/value pairs in the JSON object. The key is always of type xs:string; the associated value may be of any type, and is the result of converting the JSON value by recursive application of these rules. For example, the JSON text {"x":2, "y":5} is transformed to the value map{"x":2, "y":5}.

    If duplicate keys are encountered in a JSON object, they are handled as determined by the duplicates option defined above.

  2. A JSON array is transformed to an array whose members are the result of converting the corresponding member of the array by recursive application of these rules. For example, the JSON text ["a", "b", null] is transformed to the value ["a", "b", ()].

  3. A JSON string is converted to an xs:string value. The handling of special characters depends on the escape and fallback options, as described in the table above.

  4. A JSON number is processed using the function supplied in the number-parser option; by default it is converted to an xs:double value using the rules for casting from xs:string to xs:double.

  5. The JSON boolean values true and false are converted to the corresponding xs:boolean values.

  6. The JSON value null is converted to the empty sequence.

Error Conditions

A dynamic error [err:FOJS0001] occurs if the value of $json does not conform to the JSON grammar, unless the option "liberal":true() is present and the processor chooses to accept the deviation.

A dynamic error [err:FOJS0003] occurs if the option "duplicates":"reject" is present and the value of $json contains a JSON object with duplicate keys.

A dynamic error [err:FOJS0005] occurs if the $options map contains an entry whose key is defined in this specification and whose value is not valid for that key, or if it contains an entry with the key fallback when the option "escape":true() is also present.

Notes

The result of the function will be an instance of one of the following types. An instance of test (or in XQuery, typeswitch) can be used to distinguish them:

  • map(xs:string, item()?) for a JSON object

  • array(item()?) for a JSON array

  • xs:string for a JSON string

  • xs:double for a JSON number

  • xs:boolean for a JSON boolean

  • empty-sequence() for a JSON null (or for empty input)

If the input starts with a byte order mark, this function ignores it. The byte order mark may have been added to the data stream in order to facilitate decoding of an octet stream to a character string, but since this function takes a character string as input, the byte order mark serves no useful purpose.

The possibility of the input containing characters that are not valid in XML (for example, unpaired surrogates) arises only when such characters are expressed using JSON escape sequences. The is because the input to the function is an instance of xs:string, which by definition can only contain characters that are valid in XML.

Examples

The expression parse-json('{"x":1, "y":[3,4,5]}') returns map{"x":1e0,"y":[3e0,4e0,5e0]}.

The expression parse-json('"abcd"') returns "abcd".

The expression parse-json('{"x":"\\", "y":"\u0025"}') returns map{"x":"\","y":"%"}.

The expression parse-json('{"x":"\\", "y":"\u0025"}', map{'escape':true()}) returns map{"x":"\\","y":"%"}.

The expression parse-json('{"x":"\\", "y":"\u0000"}') returns map{"x":"\","y":codepoints-to-string(65533)}.

The expression parse-json('{"x":"\\", "y":"\u0000"}', map{'escape':true()}) returns map{"x":"\\","y":"\u0000"}.

The expression parse-json('{"x":"\\", "y":"\u0000"}', map{'fallback':function($s){'['||$s||']'}}) returns map{"x":"\","y":"[\u0000]"}.

17.5.2 fn:json-doc

Summary

Reads an external resource containing JSON, and returns the result of parsing the resource as JSON.

Signatures
fn:json-doc(
$href as xs:string?
) as item()?
fn:json-doc(
$href as xs:string?,
$options as map(*)
) as item()?
Properties

This function is ·deterministic·, ·context-dependent·, and ·focus-independent·. It depends on static base URI.

Rules

The effect of the single-argument call fn:json-doc($H) is the same as the effect of the two-argument call fn:json-doc($H, map{}) where an empty map is supplied as the second argument.

The effect of the two-argument function call fn:json-doc($H, $M)is equivalent to the function composition fn:unparsed-text($H) => fn:parse-json($M); except that:

  1. The function may accept a resource in any encoding. [RFC 7159] requires UTF-8, UTF-16, or UTF-32 to be accepted, but it is not an error if a different encoding is used. Unless external encoding information is available, the function must assume that the encoding is one of UTF-8, UTF-16, or UTF-32, and must distinguish these cases by examination of the initial octets of the resource.

  2. If the resource contains characters that are not valid in the version of XML used by the processor, then rather than raising an error as fn:unparsed-text#1 does, the function replaces such characters by the equivalent JSON escape sequence prior to parsing.

    Note:

    Equivalently, the implementation can use some other internal representation of strings that allows non-XML characters to be manipulated.

If $href is the empty sequence, the function returns the empty sequence.

Error Conditions

The function may raise any error defined for the fn:unparsed-text or fn:parse-json functions.

Notes

If the input cannot be decoded (that is, converted into a sequence of Unicode codepoints, which may or may not represent characters), then a dynamic error occurs as with the fn:unparsed-text function.

If the input can be decoded, then the possibility still arises that the resulting sequence of codepoints includes codepoints that do not represent characters that are valid in the version of XML that the processor supports. Such codepoints are translated into JSON escape sequences (for example, \uFFFF), and the JSON escape sequence is then passed to the fallback function specified in the $options argument, which in turn defaults to a function that returns the Unicode REPLACEMENT CHARACTER (xFFFD).

17.5.3 fn:json-to-xml

Summary

Parses a string supplied in the form of a JSON text, returning the results in the form of an XML document node.

Signatures
fn:json-to-xml(
$json as xs:string?
) as document-node()?
fn:json-to-xml(
$json as xs:string?,
$options as map(*)
) as document-node()?
Properties

This function is ·nondeterministic·, ·context-dependent·, and ·focus-independent·. It depends on static base URI.

Rules

The effect of the one-argument form of this function is the same as calling the two-argument form with an empty map as the value of the $options argument.

The first argument is a JSON-text as defined in [RFC 7159], in the form of a string. The function parses this string to return an XDM node.

If $json is an empty sequence, the function returns the empty sequence.

The $options argument can be used to control the way in which the parsing takes place. The ·option parameter conventions· apply.

The entries that may appear in the $options map are as follows:

Key Value Meaning
liberal Determines whether deviations from the syntax of RFC7159 are permitted.
  • Type: xs:boolean

  • Default: false

false The input must consist of an optional byte order mark (which is ignored) followed by a string that conforms to the grammar of JSON-text in [RFC 7159]. An error must be raised (see below) if the input does not conform to the grammar.
true The input may contain deviations from the grammar of [RFC 7159], which are handled in an ·implementation-defined· way. (Note: some popular extensions include allowing quotes on keys to be omitted, allowing a comma to appear after the last item in an array, allowing leading zeroes in numbers, and allowing control characters such as tab and newline to be present in unescaped form.) Since the extensions accepted are implementation-defined, an error may be raised (see below) if the input does not conform to the grammar.
duplicates Determines the policy for handling duplicate keys in a JSON object. To determine whether keys are duplicates, they are compared using the Unicode codepoint collation, after expanding escape sequences, unless the escape option is set to true, in which case keys are compared in escaped form.
  • Type: xs:string

  • Default: If validate is true then reject, otherwise retain.

reject An error is raised [err:FOJS0003] if duplicate keys are encountered.
use-first If duplicate keys are present in a JSON object, all but the first of a set of duplicates are ignored.
retain If duplicate keys are present in a JSON object, the XML result of the function will also contain duplicates (making it invalid against the schema). This value is therefore incompatible with the option validate=true [err:FOJS0005]
validate Determines whether the generated XML tree is schema-validated.
  • Type: xs:boolean

  • Default: Implementation-defined.

true Indicates that the resulting XDM instance must be typed; that is, the element and attribute nodes must carry the type annotations that result from validation against the schema given at C.2 Schema for the result of fn:json-to-xml, or against an ·implementation-defined· schema if the liberal option has the value true.
false Indicates that the resulting XDM instance must be untyped.
escape Determines whether special characters are represented in the XDM output in backslash-escaped form.
  • Type: xs:boolean

  • Default: false

false All characters in the input that are valid in the version of XML supported by the implementation, whether or not they are represented in the input by means of an escape sequence, are represented as unescaped characters in the result. Any characters or codepoints that are not valid XML characters (for example, unpaired surrogates) are passed to the fallback function as described below; in the absence of a fallback function, they are replaced by the Unicode REPLACEMENT CHARACTER (xFFFD). The attributes escaped and escaped-key will not be present in the XDM output.
true JSON escape sequences are used in the result to represent special characters in the JSON input, as defined below, whether or not they were represented using JSON escape sequences in the input. The characters that are considered "special" for this purpose are:
  • all codepoints in the range x00 to x1F or x7F to x9F;

  • all codepoints that do not represent characters that are valid in the version of XML supported by the processor, including codepoints representing unpaired surrogates;

  • the backslash character itself (x5C).

Such characters are represented using a two-character escape sequence where available (for example, \t), or a six-character escape sequence otherwise (for example \uDEAD). Characters other than these will not be escaped in the result, even if they were escaped in the input. In the result:
  • Any string element whose string value contains a backslash character must have the attribute value escaped="true".

  • Any element that contains a key attribute whose string value contains a backslash character must have the attribute escaped-key="true".

  • The values of the escaped and escaped-key attributes are immaterial when there is no backslash present, and it is never necessary to include either attribute when its value is false.

fallback Provides a function which is called when the input contains an escape sequence that represents a character that is not valid in the version of XML supported by the implementation. It is an error to supply the fallback option if the escape option is present with the value true.
  • Type: function(xs:string) as xs:string

  • Default: The default is effectively the function function($s){"&#xFFFD;"}: that is, a function that replaces the escape sequence with the Unicode REPLACEMENT CHARACTER.

User-supplied function The function is called when the JSON input contains an escape sequence that is valid according to the JSON grammar, but which does not represent a character that is valid in the version of XML supported by the processor. In the case of surrogates, the function is called once for any six-character escape sequence that is not properly paired with another surrogate. The string supplied as the argument will always be a two- or six- character escape sequence, starting with a backslash, that conforms to the rules in the JSON grammar (as extended by the implementation if liberal:true() is specified): for example \b or \uFFFF or \uDEAD. The function is not called for an escape sequence that is invalid against the grammar (for example \x0A). The function returns a string which is inserted into the result in place of the invalid character. The function also has the option of raising a dynamic error by calling fn:error.

The various structures that can occur in JSON are transformed recursively to XDM values according to the rules given in 17.4.2 XML Representation of JSON.

The function returns a document node, whose only child is the element node representing the outermost construct in the JSON text.

The function is ·non-deterministic with respect to node identity·: that is, if the function is called twice with the same arguments, it is ·implementation-dependent· whether the same node is returned on both occasions.

The base URI of the returned document node is taken from the static base URI of the function call.

The choice of namespace prefix (or absence of a prefix) in the names of constructed nodes is ·implementation-dependent·.

The XDM tree returned by the function does not contain any unnecessary (albeit valid) nodes such as whitespace text nodes, comments, or processing instructions. It does not include any whitespace in the value of number or boolean element nodes, or in the value of escaped or escaped-key attribute nodes.

If the result is typed, every element named string will have an attribute named escaped whose value is either true or false, and every element having an attribute named key will also have an attribute named escaped-key whose value is either true or false.

If the result is untyped, the attributes escaped and escaped-key will either be present with the value true, or will be absent. They will never be present with the value false.

Error Conditions

An error is raised [err:FOJS0001] if the value of $json does not conform to the JSON grammar as defined by [RFC 7159], unless the option "liberal":true() is present and the processor chooses to accept the deviation.

An error is raised [err:FOJS0004] if the value of the validate option is true and the processor does not support schema validation or typed data.

An error is raised [err:FOJS0005] if the value of $options includes an entry whose key is defined in this specification, and whose value is not a permitted value for that key.

Notes

To read a JSON file, this function can be used in conjunction with the fn:unparsed-text function.

Many JSON implementations allow commas to be used after the last item in an object or array, although the specification does not permit it. The option spec="liberal" is provided to allow such deviations from the specification to be accepted. Some JSON implementations also allow constructors such as new Date("2000-12-13") to appear as values: specifying spec="liberal" allows such extensions to be accepted, but does not guarantee it. If such extensions are accepted, the resulting value is implementation-defined, and will not necessarily conform to the schema at C.2 Schema for the result of fn:json-to-xml.

If the input starts with a byte order mark, this function ignores it. The byte order mark may have been added to the data stream in order to facilitate decoding of an octet stream to a character string, but since this function takes a character string as input, the byte order mark serves no useful purpose.

The possibility of the input containing characters that are not valid in XML (for example, unpaired surrogates) arises only when such characters are expressed using JSON escape sequences. The is because the input to the function is an instance of xs:string, which by definition can only contain characters that are valid in XML.

Examples

The expression json-to-xml('{"x": 1, "y": [3,4,5]}') returns (with whitespace added for legibility):

<map xmlns="http://www.w3.org/2005/xpath-functions">
  <number key="x">1</number>
  <array key="y">
   <number>3</number>
   <number>4</number>
   <number>5</number>
  </array>
</map>

The expression json-to-xml('"abcd"', map{'liberal': false()}) returns <string xmlns="http://www.w3.org/2005/xpath-functions">abcd</string>.

The expression json-to-xml('{"x": "\\", "y": "\u0025"}') returns (with whitespace added for legibility):

<map xmlns="http://www.w3.org/2005/xpath-functions">
  <string key="x">\</string>
  <string key="y">%</string>
</map>

The expression json-to-xml('{"x": "\\", "y": "\u0025"}', map{'escape': true()}) returns (with whitespace added for legibility):

<map xmlns="http://www.w3.org/2005/xpath-functions">
  <string escaped="true" key="x">\\</string>
  <string key="y">%</string>
</map>

The following example illustrates use of the fallback function to handle characters that are invalid in XML.

 let 
   $jsonstr := unparsed-text('http://example.com/endpoint'),
   $options := map {
     'liberal': true(),
     'fallback': function($char as xs:string) as xs:string {
       let 
         $c0chars := map {
           '\u0000':'[NUL]',
           '\u0001':'[SOH]',
           '\u0002':'[STX]',
           ...
           '\u001E':'[RS]',
           '\u001F':'[US]'
         },
         $replacement := $c0chars($char)
      return 
        if (exists($replacement))
        then $replacement
        else error(xs:QName('err:invalid-char'), 
          'Error: ' || $char || ' is not a C0 control character.')
     }
   }
 return json-to-xml($jsonstr, $options)

17.5.4 fn:xml-to-json

Summary

Converts an XML tree, whose format corresponds to the XML representation of JSON defined in this specification, into a string conforming to the JSON grammar.

Signatures
fn:xml-to-json(
$node as node()?
) as xs:string?
fn:xml-to-json(
$node as node()?,
$options as map(*)
) as xs:string?
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

The effect of the one-argument form of this function is the same as calling the two-argument form with an empty map as the value of the $options argument.

The first argument $node is a node; the subtree rooted at this node will typically be the XML representation of a JSON document as defined in 17.4.2 XML Representation of JSON.

If $node is the empty sequence, the function returns the empty sequence.

The $options argument can be used to control the way in which the conversion takes place. The ·option parameter conventions· apply.

The entries that may appear in the $options map are as follows:

Key Value Meaning
indent Determines whether additional whitespace should be added to the output to improve readability.
  • Type: xs:boolean

  • Default: false

false The processor must not insert any insignificant whitespace between JSON tokens.
true The processor may insert whitespace between JSON tokens in order to improve readability. The specification imposes no constraints on how this is done.
number-formatter Determines how numeric values should be formatted.
  • Type: (function(xs:string) as xs:string)?

  • Default: ()

User-supplied function The supplied function is called to process the string value of all fn:number elements in the input. For example, setting the value to fn:identity#1 causes the value to be output unchanged. There is no requirement that the result should be valid JSON.
() If no function is supplied, numbers are formatted by converting the string value of the fn:number element to an xs:double, and then converting the result to a string using the casting rules. Note that this will result in exponential notation being used for values outside the range 1e-6 to 1e+6. A dynamic error occurs for values such as infinity and NaN where the resulting JSON would be invalid.

The node supplied as $node must be one of the following: [err:FOJS0006]

  1. An element node whose name matches the name of a global element declaration in the schema given in C.2 Schema for the result of fn:json-to-xml ("the schema") and that is valid as defined below:

    1. If the type annotation of the element matches the type of the relevant element declaration in the schema (indicating that the element has been validated against the schema), then the element is considered valid.

    2. Otherwise, the processor may attempt to validate the element against the schema, in which case it is treated as valid if and only if the outcome of validation is valid.

    3. Otherwise (if the processor does not attempt validation using the schema), the processor must ensure that the content of the element, after stripping all attributes (at any depth) in namespaces other than http://www.w3.org/2005/xpath-functions, is such that validation against the schema would have an outcome of valid.

      Note:

      The process described here is not precisely equivalent to schema validation. For example, schema validation will fail if there is an invalid xsi:type or xsi:nil attribute, whereas this process will ignore such attributes.

  2. An element node E having a key attribute and/or an escaped-key attribute provided that E would satisfy one of the above conditions if the key and/or escaped-key attributes were removed.

  3. A document node having exactly one element child and no text node children, where the element child satisfies one of the conditions above.

Furthermore, $node must satisfy the following constraint (which cannot be conveniently expressed in the schema). Every element M that is a descendant-or-self of $node and has local name map and namespace URI http://www.w3.org/2005/xpath-functions must satisfy the following rule: there must not be two distinct children of M (say C1 and C2) such that the normalized key of C1 is equal to the normalized key of C2. The normalized key of an element C is as follows:

  • If C has the attribute value escaped-key="true", then the value of the key attribute of C, with all JSON escape sequences replaced by the corresponding Unicode characters according to the JSON escaping rules.

  • Otherwise (the escaped-key attribute of C is absent or set to false), the value of the key attribute of C.

Nodes in the input tree are handled by applying the following rules, recursively. In these rules the term "an element named N" means "an element node whose local name is N and whose namespace URI is http://www.w3.org/2005/xpath-functions".

  1. A document node having a single element node child is processed by processing that child.

  2. An element named null results in the output null.

  3. An element $E named boolean results in the output true or false depending on the result of xs:boolean(fn:string($E)).

  4. An element $E named number is processed as follows:

    1. If the number-formatter option is present and non-empty, the supplied function is called, with the string value of $E as its argument, and the result is output (whether or not it is valid JSON).

    2. Otherwise, the result of the expression xs:string(xs:double(fn:string($E))) is output.

    Note:

    The default formatting results in exponential format being used for numbers whose absolute value is outside the range 1e-6 to 1e+6; although this is valid according to the JSON specification, some receiving applications may be unable to process it. Possible reasons for using a number-formatter might be:

    • To avoid use of exponential notation in the output.

    • To avoid loss of precision when the numbers to be output have greater precision than an xs:double.

    • To improve the human readability of the output, for example by calling fn:format-number to limit the number of decimal places in the result.

    • To avoid errors when dealing with values that JSON cannot handle, such as Infinity and NaN (for example, by emitting these as strings within quotation marks).

  5. An element named string results in the output of the string value of the element, enclosed in quotation marks, with any special characters in the string escaped as described below.

  6. An element named array results in the output of the children of the array element, each processed by applying these rules recursively: the items in the resulting list are enclosed between square brackets, and separated by commas.

  7. An element named map results in the output of a sequence of map entries corresponding to the children of the map element, enclosed between curly braces and separated by commas. Each entry comprises the value of the key attribute of the child element, enclosed in quotation marks and escaped as described below, followed by a colon, followed by the result of processing the child element by applying these rules recursively.

  8. Comments, processing instructions, and whitespace text node children of map and array are ignored.

Strings are escaped as follows:

  1. If the attribute escaped="true" is present for a string value, or escaped-key="true" for a key value, then:

    1. any valid JSON escape sequence present in the string is copied unchanged to the output;

    2. any invalid JSON escape sequence results in a dynamic error [err:FOJS0007];

    3. any unescaped occurrence of quotation mark, backspace, form-feed, newline, carriage return, tab, or solidus is replaced by \", \b, \f, \n, \r, \t, or \/ respectively;

    4. any other codepoint in the range 1-31 or 127-159 is replaced by an escape in the form \uHHHH where HHHH is the upper-case hexadecimal representation of the codepoint value.

  2. Otherwise (that is, in the absence of the attribute escaped="true" for a string value, or escaped-key="true" for a key value):

    1. any occurrence of backslash is replaced by \\

    2. any occurrence of quotation mark, backspace, form-feed, newline, carriage return, or tab is replaced by \", \b, \f, \n, \r, or \t respectively;

    3. any other codepoint in the range 1-31 or 127-159 is replaced by an escape in the form \uHHHH where HHHH is the upper-case hexadecimal representation of the codepoint value.

Error Conditions

A dynamic error is raised [err:FOJS0005] if the value of $options includes an entry whose key is defined in this specification, and whose value is not a permitted value for that key.

A dynamic error is raised [err:FOJS0006] if the value of $node is not a document or element node or is not valid according to the schema for the XML representation of JSON, or if a map element has two children whose normalized key values are the same.

A dynamic error is raised [err:FOJS0007] if the value of $node includes a string labeled with escaped="true", or a key labeled with escaped-key="true", where the content of the string or key contains an invalid JSON escape sequence: specifically, where it contains a backslash (\) that is not followed by one of the characters ", \, /, b, f, n, r, t, or u, or where it contains the characters \u not followed by four hexadecimal digits (that is [0-9A-Fa-f]{4}).

Notes

The rule requiring schema validity has a number of consequences, including the following:

  1. The input cannot contain no-namespace attributes, or attributes in the namespace http://www.w3.org/2005/xpath-functions, except where explicitly allowed by the schema. Attributes in other namespaces, however, are ignored.

  2. Nodes that do not affect schema validity, such as comments, processing instructions, namespace nodes, and whitespace text node children of map and array, are ignored.

  3. Numeric values are restricted to those that are valid in JSON: the schema disallows positive and negative infinity and NaN.

  4. Duplicate key values are not permitted. Most cases of duplicate keys are prevented by the rules in the schema; additional cases (where the keys are equal only after expanding JSON escape sequences) are prevented by the prose rules of this function. For example, the key values \n and \u000A are treated as duplicates even though the rules in the schema do not treat them as such.

The rule allowing the top-level element to have a key attribute (which is ignored) allows any element in the output of the fn:json-to-xml function to be processed: for example, it is possible to take a JSON document, convert it to XML, select a subtree based on the value of a key attribute, and then convert this subtree back to JSON, perhaps after a transformation. The rule means that an element with the appropriate name will be accepted if it has been validated against one of the types mapWithinMapType, arrayWithinMapType, stringWithinMapType, numberWithinMapType, booleanWithinMapType, or nullWithinMapType.

Examples

The input <array xmlns="http://www.w3.org/2005/xpath-functions"><number>1</number><string>is</string><boolean>1</boolean></array> produces the result [1,"is",true].

The input <map xmlns="http://www.w3.org/2005/xpath-functions"><number key="Sunday">1</number><number key="Monday">2</number></map> produces the result {"Sunday":1,"Monday":2}.

17.5.5 fn:json

Summary

Creates a JSON representation of an arbitrary XDM value.

Signatures
fn:json(
$input as item()*
) as xs:string
fn:json(
$input as xs:string?,
$options as map(*)
) as xs:string
Properties

This function is ·deterministic·, ·context-independent·, and ·focus-independent·.

Rules

This function returns a string, in JSON format, containing a representation of the supplied input $input. The function is error-free (it accepts any input sequence whatsoever), but it is not lossless: there are cases when two different XDM values will have the same JSON representation. For example, the sequence (1, 2) and the array [1, 2] are both output as [1,2].

The entries that may appear in the $options map are as follows:

Key Value Meaning
indent Determines whether additional whitespace should be added to the output to improve readability.
  • Type: xs:boolean

  • Default: false

false The processor must not insert any insignificant whitespace between JSON tokens.
true The processor may insert whitespace between JSON tokens in order to improve readability. The specification imposes no constraints on how this is done.
element-map Determines whether elements whose children are element nodes with distinct names should be treated specially.
  • Type: xs:boolean

  • Default: true

false The processor treats such elements in the same way as any other element.
true The processor generates a JSON object in which the child element names are used as JSON property names.

An input sequence is handled as follows:

  • An empty sequence is output as the JSON value null.

  • A singleton sequence is output following the rules for processing items, below.

  • A sequence of two or more items results in a JSON array, whose members are constructed from the items by applying the rules below.

Items are processed as follows:

  • Atomic values

    • An xs:boolean value is output as the JSON value true or false.

    • A numeric value, other than INF, -INF, or NaN, is output as a JSON number.

    • Any other atomic value is cast to xs:string, and the result is output as a JSON string, escaped as described below.

  • Nodes

    • Document nodes

      A document node is output as a JSON object with two properties, in order:

      • A property #document set to the value of the base URI of the document if available, or an empty string otherwise..

      • A property #content whose value follows the rules for outputting the content of an element node, given below.

    • Element nodes

      An element node is output as a JSON object with the following properties, in order:

      • #element set to the local name of the element.

      • If the element name has a prefix, #prefix, set to the value of the prefix.

      • If the element name is in a namespace, #namespace, set to the value of the namespace URI.

      • For each attribute of the element, in arbitrary order, a property whose name is derived from the attribute name as follows:

        • If the attribute name is in no namespace, then "@" followed by the local name.

        • If the attribute name is in the XML namespace, then "@xml:" followed by the local name.

        • Otherwise "@Q{uri}local" where uri is the namespace URI and local is the local name.

        The property value is the result of atomizing the attribute node and applying the fn:json function to the result. (For untyped attributes, the result will always be a single string.)

      • The children of the element are processed as follows:

        • If there are no children, nothing is output.

        • If the element has a type annotation that is a simple type, or if its content comprises a single text node, then a property #value set to the result of atomizing the element node and applying the fn:json function to the result.

        • If (a) the children consist exclusively of elements and whitespace-only text nodes, and (b) the child element nodes are all in the same namespace, or all in no namespace, and (c) each child element has a local name that is distinct from the local name of any other child, and (d) the element-map option is not present in $options with the value false(), then a property #content whose value is a JSON object having one property for each child element node. The name of this property is the local name of the element, and the value of the property is obtained by applying these rules recursively, except that for an empty element, the value is represented as JSON null.

        • Otherwise, a property #content whose value is an array, with one member for each child node (including whitespace-only text nodes), obtained by applying the fn:json function to that child node.

    • Text nodes

      A JSON object with a single property #text whose value is the string value of the text node.

    • Comment nodes

      A JSON object with a single property #comment whose value is the string value of the comment.

    • Processing instruction nodes

      A JSON object with a two properties (in order): #processing-instruction set to the name of the processing instruction, and #data set to the string value of the processing instruction node.

    • Attribute nodes

      Attribute nodes that are reached via an element node are output as described under "element nodes", above.

      Free-standing attribute nodes are output as JSON objects with properties #attribute set to the local name of the attribute, #prefix (if non-empty) set to the prefix of the attribute's name, #namespace (if non-empty) set to the namespace URI, and #value set to the result of atomizing the attribute value and applying the fn:json function to the result.

    • Namespace nodes

    • Namespace nodes that are reached via an element node result in no output.

    • Free-standing namespace nodes are output as JSON objects with properties #namespace set to the namespace prefix ("" for the default namespace) and #uri set to the namespace URI.

  • Maps

    An XDM map is output as a JSON object with one property for each entry in the map.

    The property name is derived from the key value by converting the value to a string and applying escaping rules. If the property name thus generated is the same as a previously output property name, then it is made unique by appending "(N)" where N is the smallest positive integer that makes the resulting value unique.

    The property value is derived by applying the fn:json function to the value in the map entry.

    Note:

    Conflicts between property names can arise because the XDM model allows keys of different types, for example the xs:date value 2020-12-31 and the string value "2020-12-31" can co-exist. The map map{xs:duration('PT1D'):20, "PT1D":30} is converted to the JSON string {"PT1D":20,"PT1D(1)":30} or {"PT1D":30,"PT1D(1)":20}, depending on the (unpredictable) order in which the entries in the map are processed.

    Note:

    Because the order of entries in a map is unpredictable, the order in which the properties are listed in the JSON output is also unpredictable.

  • Arrays

    An XDM array is output as a JSON array. Each member of the XDM array generates one entry in the JSON array, in order, obtained by applying the fn:json function to the XDM array member.

  • Functions

    An XDM function, other than a map or array, is output as a JSON object with the following properties:

    • #function, set to the local name of the function if it has a name, or the empty string otherwise.

    • #namespace, set to the namespace URI of the function. The property is omitted for an anonymous function.

    • #arity, set to the arity of the function as a JSON number.

    • #arguments whose value is an array of strings, which identify the names and types of the function arguments, in the format $Q{uri}local as SequenceType: for example ["$x as double", "$y as string"]. Namespace prefixes must not be used: unprefixed element names and variable names are taken to be in no namespace, and unprefixed type names are taken to be in the namespace http://www.w3.org/2001/XMLSchema.

    • #result whose value is a string identifying the type of the function result, using the same conventions as for #arguments.

    • Optionally at implementer discretion, #implementation whose value is a string representing the function's implementation in implementation-defined format.

    Strings are escaped as follows:

    1. Any occurrence of backslash is replaced by \\

    2. Any occurrence of quotation mark, backspace, form-feed, newline, carriage return, or tab is replaced by \", \b, \f, \n, \r, or \t respectively;

    3. Any other codepoint in the range 1-31 or 127-159 is replaced by an escape in the form \uHHHH where HHHH is the upper-case hexadecimal representation of the codepoint value.

Notes

In the JSON output, names of properties defined in this specification are prefixed with #; names not so prefixed are derived from names appearing in the input.

Namespace information may be lost (specifically, namespaces that are declared but not used are not retained in the output).

The distinction between sequences and arrays is lost.

The distinction between different atomic types is lost, except for the boolean / number / string distinction present in JSON.

In elements whose children are elements with distinct names, whitespace text nodes are lost, and the namespace URIs and prefixes of the child elements are lost.

Examples

The expression fn:json(()) returns 'null'.

The expression fn:json(12) returns '12'.

The expression fn:json((12, "December")) returns '[12,"December"]'.

The expression fn:json(true()) returns 'true'.

The expression fn:json(map{"a":1,"b":number('NaN'),"c":(1,2,3)}) returns '{"a":1,"b":"NaN","c":[1,2,3]}'. ((or some permutation thereof)).

The expression fn:json(<a x="2">banana</a>) returns '{"#element":"a","@x":"2","#value":"banana"}'.

The expression fn:json(<a><b/><c>2</c></a>) returns '{"#element":"a","#content":{"b":null,"c":"2"}}'.

The expression fn:json(<a><b/><b/><c/></a>) returns '{"#name":"a","#content":[{"#name":"b"},{"#name":"b"},{"#name":"c}]}'.

The expression fn:json(<a>A <i>nice</i> one!</a>) returns '{"#name":"a","#content":["A ",{"#name":"i", "#value":"nice"}," one!"]}'.

History

Proposed for 4.0; not yet reviewed.