Pattern Syntax
The real strength of @silver-formily/path is not plain dot paths. It is the fact that path patterns and access paths share the same abstraction.
Interactive demo
This playground shows the parsed segments, flags, and match result for the current pattern. It is useful for experimenting with *, **, ~, groups, and relative paths.
<script setup lang="ts">
import { Path } from '@silver-formily/path'
import { computed, ref } from 'vue'
const pattern = ref('*(phases.*.type,phases.*.steps.*.type)')
const base = ref('')
const target = ref('phases.0.steps.1.type')
const parsed = computed(() => {
try {
const value = Path.parse(pattern.value, base.value || undefined)
return { value, error: '' }
}
catch (error) {
return { value: null, error: error instanceof Error ? error.message : String(error) }
}
})
const matchResult = computed(() => {
if (parsed.value.error || !parsed.value.value)
return { matched: false, error: parsed.value.error }
try {
return {
matched: parsed.value.value.match(target.value),
error: '',
}
}
catch (error) {
return {
matched: false,
error: error instanceof Error ? error.message : String(error),
}
}
})
const presets = [
{ text: 'single wildcard', value: 'user.*', target: 'user.name' },
{ text: 'optional deep wildcard', value: 'aa.**', target: 'aa.bb.cc' },
{ text: 'expand', value: 't.0.value~', target: 't.0.value_list' },
{ text: 'exclude group', value: '*(!basic.name,versionTag)', target: 'basic.id' },
{ text: 'relative path', value: '.value', target: 'users.0.value', base: 'users.0' },
]
function usePreset(item: { text: string, value: string, target: string, base?: string }) {
pattern.value = item.value
target.value = item.target
base.value = item.base ?? ''
}
</script>
<template>
<div class="playground">
<div class="tagRow">
<button v-for="item in presets" :key="item.text" class="btn secondary" @click="usePreset(item)">
{{ item.text }}
</button>
</div>
<div class="grid">
<label class="field">
<span class="fieldLabel">Pattern</span>
<input v-model="pattern" class="input">
</label>
<label class="field">
<span class="fieldLabel">Base</span>
<input v-model="base" class="input" placeholder="optional">
</label>
<label class="field">
<span class="fieldLabel">Target</span>
<input v-model="target" class="input">
</label>
</div>
<div v-if="parsed.error || matchResult.error" class="errorBox">
{{ parsed.error || matchResult.error }}
</div>
<div v-else-if="parsed.value" class="panelGrid">
<div class="panel">
<div class="panelTitle">
Parsed Path
</div>
<div class="panelBody metricValue">
{{ parsed.value.toString() }}
</div>
</div>
<div class="panel">
<div class="panelTitle">
Segments
</div>
<div class="panelBody metricValue">
{{ JSON.stringify(parsed.value.toArr(), null, 2) }}
</div>
</div>
<div class="panel">
<div class="panelTitle">
Match Target
</div>
<div class="panelBody">
<span :class="matchResult.matched ? 'statusTrue' : 'statusFalse'">
{{ matchResult.matched }}
</span>
</div>
</div>
<div class="panel">
<div class="panelTitle">
Flags
</div>
<div class="panelBody tagRow">
<span class="tag">isMatchPattern: {{ parsed.value.isMatchPattern }}</span>
<span class="tag">isWildMatchPattern: {{ parsed.value.isWildMatchPattern }}</span>
<span class="tag">haveRelativePattern: {{ parsed.value.haveRelativePattern }}</span>
<span class="tag">haveExcludePattern: {{ parsed.value.haveExcludePattern }}</span>
<span class="tag">isRegExp: {{ parsed.value.isRegExp }}</span>
</div>
</div>
</div>
</div>
</template>
<style scoped src="../../shared/path-demo.css"></style>Plain paths
Path.parse('user.profile.nickname')
Path.parse(['user', 'profile', 'nickname'])This is the most common data-path form and is mainly used for getIn / setIn / deleteIn.
Indexed paths
Array indices can be written either with dot notation or bracket notation:
const target = { array: [] }
Path.setIn(target, 'array.0.aa', '000')
Path.setIn(target, 'array[1].aa', '111')
Path.getIn(target, 'array.0.aa')
// '000'
Path.getIn(target, 'array.1.aa')
// '111'Destructuring expressions
Destructuring expressions behave like a normal path segment, but they trigger split/rebuild semantics during data operations:
const target = {}
Path.setIn(target, 'parent.[aa,bb]', [11, 22])
// { parent: { aa: 11, bb: 22 } }
Path.getIn(target, 'parent.[aa,bb]')
// [11, 22]
Path.parse('parent.[aa,bb]').toString()
// 'parent.[aa,bb]'This syntax is especially useful when frontend and backend structures do not align one to one.
Single-level wildcard *
* matches exactly one segment:
Path.parse('user.*').match('user.name')
// true
Path.parse('user.*').match('user.profile.name')
// falseOptional deep wildcard **
** means “zero or more segments from here”:
Path.parse('aa.**').match(['aa'])
// true
Path.parse('aa.**').match(['aa', 'bb', 'cc'])
// trueExpand ~
~ enables prefix expansion matching:
Path.parse('xxx.eee~').match('xxx.eee')
// true
Path.parse('t.0.value~').match(['t', 0, 'value_list'])
// trueThis is common in Formily field aliases and expanded child paths.
Relative expressions
With a base path, a pattern can be relative:
Path.parse('.value', 'users.0').toString()
// 'users.0.value'More advanced relative expressions are especially useful for neighboring array items:
Path.parse('.dd', 'aa.bb.cc').toString()
// 'aa.bb.dd'
Path.parse('..[].dd', 'aa.1.cc').toString()
// 'aa.1.dd'
Path.parse('..[+].dd', 'aa.1.cc').toString()
// 'aa.2.dd'
Path.parse('..[+10].dd', 'aa.1.cc').toString()
// 'aa.11.dd'Three practical rules:
- One dot means the current path.
ndots mean “go backn - 1steps”.+and-expressions inside brackets offset the current array index.
Global match
A standalone * means “match every path”:
Path.parse('*').match('aa')
// true
Path.parse('*').match('aa.bb')
// truePartial match
Placing * at a concrete position means “match any single segment here”:
Path.parse('aa.*.cc').match('aa.bb.cc')
// true
Path.parse('aa.*.cc').match('aa.kk.cc')
// trueGroups
Group syntax lets one pattern cover multiple candidates:
Path.parse('*(aa,bb,cc)').match('bb')
// true
Path.parse('*(phases.*.type,phases.*.steps.*.type)').match('phases.0.steps.1.type')
// true
Path.parse('aa.*(bb,kk,dd,ee.*(oo,gg).gg).cc').match('aa.ee.oo.gg.cc')
// trueExclude patterns
Use ! inside a group to express exclusions:
Path.parse('*(!aaa)').match('ggg')
// true
Path.parse('*(!aaa)').match('aaa')
// false
Path.parse('*(!basic.name,versionTag)').match('basic.id')
// trueExclude patterns are especially useful when you want to broadly match most fields but skip a few special branches.
Regular expression patterns
Regular expressions are also valid patterns:
Path.parse(/^\d+$/).match('212')
// true
Path.parse(/^\d+$/).match('212dd')
// falseRange matching
Range matching is mostly for array indices:
Path.parse('aa.*[1:2].bb').match('aa.1.bb')
// true
Path.parse('aa.*[1:2].bb').match('aa.3.bb')
// false
Path.parse('aa.*[1:].bb').match('aa.3.bb')
// true
Path.parse('aa.*[:100].bb').match('aa.1000.bb')
// falseLeaving x or y empty makes it an open interval.
Escaped matching
If a path segment itself contains syntax keywords, you can escape it with backslashes or [[...]]:
Path.parse('aa.\\,\\*\\{\\}\\.\\(\\).bb').match('aa.\\,\\*\\{\\}\\.\\(\\).bb')
// true
Path.parse('aa.[[,*{}.()]].bb').match('aa.[[,*{}.()]].bb')
// trueDestructuring match
If the path contains a destructuring expression, you can match it directly without extra escaping:
Path.parse('target.[aa,bb]').match('target.[aa,bb]')
// true