Open joewiz opened 3 years ago
See also this discussion at StackOverflow, where a user was struggling to use map:put or map:remove on deeper entries in a map; asked, "Is XQuery 3.1 designed for advanced JSON editing?"; and worried that XQuery "might not be the right choice" for his use case. Highlights from the responses:
And it has moved to the mailing list
There's a gotcha we have to be careful about. Maps and arrays must be able to contain nodes without copying them like XML node constructors.
Maps and arrays are often used to identify nodes to be modified. I wrote this today:
declare function local:range($start)
{
$start,
remainder($start)
};
declare function local:ranges($root)
{
for $start in $root//w
where continues($start)
and fn:not(continues($start/preceding-sibling))
return array { range($start) }
};
declare updating function local:merge($range)
{
merge_morphcodes(fn:head($range), fn:tail($range))
,
merge_node_text(fn:head(fn:head($range)), fn:tail($range))
,
delete nodes fn:tail($range)
};
for $r at $i in ranges( db:open("oshb-morphology") )
return merge(array:flatten($r))
This only works because maps and arrays do not create new identities. If I used an element constructor instead of an array in this query, it would create a new copy of each child element and the updates would modify only the transient copy. Using an array, the elements in the array retain their identity and updates are applied to the instance in the database.
Here are functions that we have used in the past to delete, replace and update nested map entries:
declare namespace map = 'http://www.w3.org/2005/xpath-functions/map';
declare namespace maps = 'maps';
(:~
: Recursively removes map entries.
: @param $input input (map, any other item)
: @param $keys path to entry to delete
: @return updated item
:)
declare function maps:delete(
$input as item()*,
$keys as xs:string*
) as item()* {
if($input instance of map(*)) then (
map:merge(map:for-each($input, function($k, $v) {
if($k = head($keys)) then (
if(count($keys) > 1) then map:entry($k, maps:delete($v, tail($keys))) else ()
) else (
map:entry($k, $v)
)
}))
) else (
$input
)
};
(:~
: Recursively replaces map entries.
: @param $input input (map, any other item)
: @param $keys path to entry to delete
: @param $value new value
: @return updated item
:)
declare function maps:replace(
$input as item()*,
$keys as xs:string*,
$value as item()*
) as item()* {
if($input instance of map(*)) then (
map:merge(map:for-each($input, function($k, $v) {
map:entry($k, if($k = head($keys)) then (
if(count($keys) > 1) then (
maps:replace($v, tail($keys), $value)
) else (
$value
)
) else (
$v
))
}))
) else (
$input
)
};
(:~
: Recursively updates map entries.
: @param $input input (map, any other item)
: @param $keys path to entry to update
: @param $value function that creates the new value
: @return updated item
:)
declare function maps:update(
$input as item()*,
$keys as xs:string*,
$update as function(item()*) as item()*
) as item()* {
if($input instance of map(*)) then (
map:merge(map:for-each($input, function($k, $v) {
map:entry($k, if($k = head($keys)) then (
if(count($keys) > 1) then (
maps:update($v, tail($keys), $update)
) else (
$update($v)
)
) else (
$v
))
}))
) else (
$input
)
};
Example invocations:
maps:delete( map { 'a': map { 'b': 'c' } }, ('a', 'b') )
maps:replace( map { 'a': map { 'b': 'c' } }, ('a', 'b'), 'd')
maps:update(map { 'a': map { 'b': ('c', 'd') } }, ('a', 'b'), function($v) { count($v) })
Please see the comments in #832 for possible solutions to this feature request.
As discussed in the xml.com Slack workspace's xpath-ng channel, there is interest in extending the XQuery Update Facility to allow manipulation of maps and arrays—in effect, to facilitate the editing of large, deep JSON documents.
For example, @DrRataplan provided this use case (the first code snippet can be viewed at fontoxml's playground):
See also this discussion at StackOverflow, where a user was struggling to use
map:put
ormap:remove
on deeper entries in a map; asked, "Is XQuery 3.1 designed for advanced JSON editing?"; and worried that XQuery "might not be the right choice" for his use case. Highlights from the responses:@michaelhkay wrote:
@ChristianGruen wrote:
@michaelhkay responded:
In Slack @liamquin also wrote:
@jonathanrobie also wrote:
@adamretter added: