+
+ Schema is auto generated from reference and is json-schema compliant.
+
+
-
+
+
+ Types are auto generated from schema for typescript usage.
+
+
-
-
+
+
+ Using optional loader, we can support jsdoc to describe object.
+
+
+
+
+
+
+ We can apply reference object to user input to apply defaults.
+
+
@@ -59,41 +72,51 @@
diff --git a/playground/consts.ts b/playground/consts.ts
new file mode 100644
index 0000000..47c2ff0
--- /dev/null
+++ b/playground/consts.ts
@@ -0,0 +1,29 @@
+export const defaultReference = `
+export default {
+ name: 'default',
+ price: 12.5,
+ /**
+ * checked state
+ */
+ checked: false,
+ dimensions: {
+ /** width in px */
+ width: 10,
+ /** height in px */
+ height: 10
+ },
+ tags: {
+ $resolve: (val) => ['tag1'].concat(val).filter(Boolean)
+ }
+}
+`.trim()
+
+export const defaultInput = `
+export default {
+ name: 'foo',
+ dimensions: {
+ height: 25
+ },
+ tags: ['custom']
+}
+`.trim()
diff --git a/playground/utils.ts b/playground/utils.ts
index 8b8960c..0d69704 100644
--- a/playground/utils.ts
+++ b/playground/utils.ts
@@ -31,7 +31,7 @@ export function tryFn (fn) {
export function persistedState (initialState = {}) {
const state = reactive({
...initialState,
- ...tryFn(() => JSON.parse(atob(window.location.hash.substr(1))))
+ ...tryFn(() => JSON.parse(atob(window.location.hash.substr(1)) || '{}'))
})
watch(state, () => {
window.location.hash = '#' + btoa(JSON.stringify(state))
@@ -64,24 +64,3 @@ export function asyncImport ({ loader, loading, error }) {
})
return m
}
-
-export const defaultInput = `
-export default {
- name: 'vulcan',
- price: 12.5,
- /**
- * checked state
- * If is null, will use last checked status
- */
- checked: false,
- dimensions: {
- /** width in px */
- width: 5,
- /** width in px */
- height: 10
- },
- tags: {
- $resolve: (val) => ['tag1'].concat(val).filter(Boolean)
- }
-}
-`.trim()
diff --git a/src/defaults.ts b/src/defaults.ts
new file mode 100644
index 0000000..6be596c
--- /dev/null
+++ b/src/defaults.ts
@@ -0,0 +1,25 @@
+import { resolveSchema } from './schema'
+import type { InputObject, Schema } from './types'
+
+export function applyDefaults (ref: InputObject, input: InputObject) {
+ const schema = resolveSchema(ref, true)
+ return _applyDefaults(schema, input)
+}
+
+export function _applyDefaults (schema: Schema, input: InputObject, root?: InputObject) {
+ if (schema.type !== 'object') {
+ return {}
+ }
+ const merged = {}
+ for (const key in schema.properties!) {
+ const prop = schema.properties[key]
+ if (prop.type === 'object') {
+ merged[key] = _applyDefaults(prop, input?.[key], root || input)
+ } else if (typeof prop.resolve === 'function') {
+ merged[key] = prop.resolve(input?.[key], merged, root || input)
+ } else {
+ merged[key] = input?.[key] || prop.default
+ }
+ }
+ return merged
+}
diff --git a/src/index.ts b/src/index.ts
index e707455..24cb4e1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,5 @@
export { resolveSchema } from './schema'
export { generateDts } from './dts'
+export { applyDefaults } from './defaults'
export * from './types'
diff --git a/src/schema.ts b/src/schema.ts
index 6d34ba6..d107140 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -1,14 +1,14 @@
import { getType, isObject, unique } from './utils'
import type { InputObject, InputValue, JSValue, Schema } from './types'
-export function resolveSchema (obj: InputObject) {
- const schema = _resolveSchema(obj, obj)
+export function resolveSchema (obj: InputObject, preserveResolve?: boolean) {
+ const schema = _resolveSchema(obj, obj, undefined, preserveResolve)
// TODO: Create meta-schema fror superset of Schema interface
// schema.$schema = 'http://json-schema.org/schema#'
return schema
}
-function _resolveSchema (input: InputValue, parent: InputObject, root?: InputObject): Schema {
+function _resolveSchema (input: InputValue, parent: InputObject, root?: InputObject, preserveResolve?: boolean): Schema {
// Node is plain value
if (!isObject(input)) {
return normalizeSchema({
@@ -26,7 +26,7 @@ function _resolveSchema (input: InputValue, parent: InputObject, root?: InputObj
const getSchema = (key) => {
schema.properties = schema.properties || {}
if (!schema.properties[key]) {
- schema.properties[key] = _resolveSchema(node[key], proxifiedNode, root || proxifiedNode)
+ schema.properties[key] = _resolveSchema(node[key], proxifiedNode, root || proxifiedNode, preserveResolve)
}
return schema.properties[key]
}
@@ -46,6 +46,9 @@ function _resolveSchema (input: InputValue, parent: InputObject, root?: InputObj
}
if (typeof node.$resolve === 'function') {
schema.default = node.$resolve(schema.default, parent, root || proxifiedNode)
+ if (preserveResolve) {
+ schema.resolve = node.$resolve
+ }
}
// Infer type from default value
diff --git a/src/types.ts b/src/types.ts
index 25384b3..abbe949 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -20,11 +20,13 @@ export type JSType =
'any' |
'array'
-// A subset of JSONSchema7
+export type ResolveFn = ((value: any, parent: InputObject, root: InputObject) => JSValue)
+
export interface Schema {
type?: JSType | JSType[]
items?: Schema
default?: JSValue
+ resolve?: ResolveFn
properties?: { [key: string]: Schema }
title?: string
description?: string
@@ -34,7 +36,7 @@ export interface Schema {
export interface InputObject {
[key: string]: any
$schema?: Schema
- $resolve?: JSValue | ((value: any, parent: InputObject, root: InputObject) => JSValue)
+ $resolve?: ResolveFn
}
export type InputValue = InputObject | JSValue