Ctrl K
ComunidadGuías…

vlib/json2/doc.v

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
module json2

// Doc is a parsed JSON document. Use `value`, `value_opt` or `decode` to read
// from it.
pub struct Doc {
	params DecoderOptions
pub:
	root Any
}

// new_doc wraps an already parsed `Any` tree.
pub fn new_doc(root Any, params DecoderOptions) Doc {
	return Doc{
		root:   root
		params: params
	}
}

// to_any returns the document root as a dynamic value.
pub fn (d Doc) to_any() Any {
	return d.root
}

// str returns the document rendered in the dialect it was parsed with.
pub fn (d Doc) str() string {
	return encode(d.root, json5: d.params.json5)
}

// value queries a value with a small path syntax: dots separate object keys,
// brackets index arrays, and a key that contains a dot can be quoted, as in
// `a."b.c"`. Returns `Null` when the path does not resolve.
pub fn (d Doc) value(key string) Any {
	return resolve_path(d.root, parse_path(key) or { return null })
}

// value_opt queries a value and returns an error when the path does not
// resolve, instead of `Null`.
pub fn (d Doc) value_opt(key string) !Any {
	steps := parse_path(key) or { return error('invalid path `${key}`') }
	return resolve_path_opt(d.root, steps) or { return error('json2: no key `${key}`') }
}

// get queries a value and returns an option, so `none` distinguishes a missing
// key from an explicit `null`.
pub fn (d Doc) get(key string) ?Any {
	steps := parse_path(key) or { return none }
	return resolve_path_opt(d.root, steps)
}

// decode decodes the document into `T`. It is the method form of the
// package-level `decode` and shares its attribute and hook support.
pub fn (d Doc) decode[T]() !T {
	return decode_any[T](d.root, d.params)
}

// reflect sets the fields of `T` from the matching document keys, leaving
// absent fields at their default value. Fields are matched by name or by a
// `@[json: 'name']` attribute. Unlike `decode`, a key that cannot be decoded
// into its field is silently skipped instead of reported, so a partially
// matching document still fills in what it can.
pub fn (d Doc) reflect[T]() T {
	mut out := T{}
	reflect_into(d.root, mut out, d.params)
	return out
}

// PathStep is one element of a parsed lookup path: either an object key or an
// array index.
pub struct PathStep {
pub:
	key   string
	index int
}

// is_index reports whether the step indexes an array.
pub fn (s PathStep) is_index() bool {
	return s.index >= 0
}

// str returns the step in path syntax.
pub fn (s PathStep) str() string {
	return if s.is_index() { '[${s.index}]' } else { s.key }
}

// parse_path splits a lookup path into steps. Quoted segments keep any dots
// they contain, so `a."b.c"` addresses the key `b.c` inside the object `a`.
//
// The path is walked with an explicit index rather than a state machine, which
// keeps the "flush the pending key" logic in one place.
pub fn parse_path(path string) ![]PathStep {
	mut steps := []PathStep{}
	runes := path.runes()
	mut i := 0
	mut key := []rune{}
	mut has_key := false
	for i < runes.len {
		ch := runes[i]
		match ch {
			`.` {
				push_key(mut steps, key, has_key)
				key = []rune{}
				has_key = false
				i++
			}
			`[` {
				push_key(mut steps, key, has_key)
				key = []rune{}
				has_key = false
				mut j := i + 1
				mut digits := []rune{}
				for j < runes.len && json5_is_digit(int(runes[j])) {
					digits << runes[j]
					j++
				}
				if digits.len == 0 || j >= runes.len || runes[j] != `]` {
					return error('expected an array index like `[0]` in path `${path}`')
				}
				steps << PathStep{
					key:   ''
					index: digits.string().int()
				}
				i = j + 1
			}
			`'`, `"` {
				if has_key {
					return error('unexpected quote in path `${path}`')
				}
				mut j := i + 1
				mut quoted := []rune{}
				for j < runes.len && runes[j] != ch {
					quoted << runes[j]
					j++
				}
				if j >= runes.len {
					return error('unterminated quoted key in path `${path}`')
				}
				steps << PathStep{
					key:   quoted.string()
					index: -1
				}
				has_key = false
				key = []rune{}
				i = j + 1
				if i < runes.len && runes[i] != `.` && runes[i] != `[` {
					return error('unexpected character after a quoted key in path `${path}`')
				}
			}
			else {
				key << ch
				has_key = true
				i++
			}
		}
	}
	push_key(mut steps, key, has_key)
	return steps
}

// push_key appends a pending object-key step to `mut steps` when there is one.
fn push_key(mut steps []PathStep, key []rune, has_key bool) {
	if has_key {
		steps << PathStep{
			key:   key.string()
			index: -1
		}
	}
}

// resolve_path walks steps through value, returning null when a step does not resolve.
pub fn resolve_path(value Any, steps []PathStep) Any {
	return resolve_path_opt(value, steps) or { null }
}

// The traversal borrows containers from the document; autofree must not release them.
@[manualfree]
fn resolve_path_opt(value Any, steps []PathStep) ?Any {
	mut current := value
	for step in steps {
		if step.is_index() {
			if current is []Any {
				if step.index >= current.len {
					return none
				}
				current = current[step.index]
				continue
			}
			return none
		}
		if current is map[string]Any {
			current = current[step.key] or { return none }
		} else {
			return none
		}
	}
	return current
}

// reflect_into fills the fields of `mut out` from `value`, skipping any key it
// cannot decode.
@[manualfree]
fn reflect_into[T](value Any, mut out T, params DecoderOptions) {
	$if T is $struct {
		obj := match value {
			map[string]Any { value }
			else { return }
		}
		// `$for` unrolls to straight-line code, so the field logic is nested
		// `if`s rather than a loop with `continue`.
		$for field in T.fields {
			key := field_key_name(field.name, field.attrs)
			if key != '' {
				$if field.is_embed {
					$if field.unaliased_typ is $struct {
						mut embedded := out.$(field.name)
						reflect_into(value, mut embedded, params)
						out.$(field.name) = embedded
					}
				} $else {
					// A key that is absent, `null`, or of a kind the field cannot
					// hold leaves the field at its default.
					if item := obj[key] {
						if item !is Null {
							mut slot := out.$(field.name)
							reflect_value(item, mut slot, params)
							out.$(field.name) = slot
						}
					}
				}
			}
		}
	} $else {
		reflect_value(value, mut out, params)
	}
}

@[manualfree]
fn reflect_value[T](value Any, mut out T, params DecoderOptions) {
	if decoded := decode_any[T](value, params) {
		out = decoded
	}
}

fn field_key_name(name string, attrs []string) string {
	info := encoder_field_info(name, attrs)
	return if info.is_skip { '' } else { info.key_name }
}