mirror of
https://github.com/vlang/v.git
synced 2025-08-05 18:57:45 -04:00
470 lines
14 KiB
V
470 lines
14 KiB
V
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
module json2
|
|
|
|
import time
|
|
|
|
fn format_message(msg string, line int, column int) string {
|
|
return '[x.json2] ${msg} (${line}:${column})'
|
|
}
|
|
|
|
fn (mut p Parser) next() {
|
|
p.prev_tok = p.tok
|
|
p.tok = p.next_tok
|
|
p.next_tok = p.scanner.scan()
|
|
}
|
|
|
|
fn (mut p Parser) next_with_err() ! {
|
|
p.next()
|
|
if p.tok.kind == .error {
|
|
return DecodeError{
|
|
line: p.tok.line
|
|
column: p.tok.full_col()
|
|
message: p.tok.lit.bytestr()
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: copied from v.util to avoid the entire module and its functions
|
|
// from being imported. remove later once -skip-unused is enabled by default.
|
|
// skip_bom - skip Byte Order Mark (BOM)
|
|
// The UTF-8 BOM is a sequence of Bytes at the start of a text-stream (EF BB BF or \ufeff)
|
|
// that allows the reader to reliably determine if file is being encoded in UTF-8.
|
|
fn skip_bom(file_content string) string {
|
|
mut raw_text := file_content
|
|
// BOM check
|
|
if raw_text.len >= 3 {
|
|
unsafe {
|
|
c_text := raw_text.str
|
|
if c_text[0] == 0xEF && c_text[1] == 0xBB && c_text[2] == 0xBF {
|
|
// skip three BOM bytes
|
|
offset_from_begin := 3
|
|
raw_text = tos(c_text[offset_from_begin], vstrlen(c_text) - offset_from_begin)
|
|
}
|
|
}
|
|
}
|
|
return raw_text
|
|
}
|
|
|
|
// new_parser - create a instance of Parser{}
|
|
fn new_parser(srce string, convert_type bool) Parser {
|
|
src := skip_bom(srce)
|
|
return Parser{
|
|
scanner: &Scanner{
|
|
text: src.bytes()
|
|
}
|
|
convert_type: convert_type
|
|
}
|
|
}
|
|
|
|
// decode is a generic function that decodes a JSON string into the target type.
|
|
pub fn decode[T](src string) !T {
|
|
$if T is Any {
|
|
return raw_decode(src)!
|
|
}
|
|
res := raw_decode(src)!.as_map()
|
|
return decode_struct[T](T{}, res)
|
|
}
|
|
|
|
// decode_struct_array is a generic function that decodes a JSON map into array struct T.
|
|
fn decode_struct_array[T](_ T, res map[string]Any) ![]T {
|
|
$if T is $struct {
|
|
mut arr := []T{}
|
|
for v in res.values() {
|
|
arr << decode_struct[T](T{}, v.as_map())!
|
|
}
|
|
return arr
|
|
} $else {
|
|
return error("The type `${T.name}` can't be decoded.")
|
|
}
|
|
}
|
|
|
|
// decode_struct is a generic function that decodes a JSON map into the struct T.
|
|
fn decode_struct[T](_ T, res map[string]Any) !T {
|
|
mut typ := T{}
|
|
$if T is $struct {
|
|
$for field in T.fields {
|
|
mut skip_field := false
|
|
mut json_name := field.name
|
|
|
|
for attr in field.attrs {
|
|
if attr.contains('skip') {
|
|
skip_field = true
|
|
}
|
|
if attr.contains('json: ') {
|
|
json_name = attr.replace('json: ', '')
|
|
if json_name == '-' {
|
|
skip_field = true
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if !skip_field {
|
|
$if field.is_enum {
|
|
if v := res[json_name] {
|
|
typ.$(field.name) = v.int()
|
|
} else {
|
|
$if field.is_option {
|
|
typ.$(field.name) = none
|
|
}
|
|
}
|
|
} $else $if field.typ is u8 {
|
|
typ.$(field.name) = res[json_name]!.u64()
|
|
} $else $if field.typ is u16 {
|
|
typ.$(field.name) = res[json_name]!.u64()
|
|
} $else $if field.typ is u32 {
|
|
typ.$(field.name) = res[json_name]!.u64()
|
|
} $else $if field.typ is u64 {
|
|
typ.$(field.name) = res[json_name]!.u64()
|
|
} $else $if field.typ is int {
|
|
typ.$(field.name) = res[json_name]!.int()
|
|
} $else $if field.typ is i8 {
|
|
typ.$(field.name) = res[json_name]!.int()
|
|
} $else $if field.typ is i16 {
|
|
typ.$(field.name) = res[json_name]!.int()
|
|
} $else $if field.typ is i32 {
|
|
typ.$(field.name) = i32(res[field.name]!.i32())
|
|
} $else $if field.typ is i64 {
|
|
typ.$(field.name) = res[json_name]!.i64()
|
|
} $else $if field.typ is ?u8 {
|
|
if json_name in res {
|
|
typ.$(field.name) = ?u8(res[json_name]!.i64())
|
|
}
|
|
} $else $if field.typ is ?i8 {
|
|
if json_name in res {
|
|
typ.$(field.name) = ?i8(res[json_name]!.i64())
|
|
}
|
|
} $else $if field.typ is ?u16 {
|
|
if json_name in res {
|
|
typ.$(field.name) = ?u16(res[json_name]!.i64())
|
|
}
|
|
} $else $if field.typ is ?i16 {
|
|
if json_name in res {
|
|
typ.$(field.name) = ?i16(res[json_name]!.i64())
|
|
}
|
|
} $else $if field.typ is ?u32 {
|
|
if json_name in res {
|
|
typ.$(field.name) = ?u32(res[json_name]!.i64())
|
|
}
|
|
} $else $if field.typ is ?i32 {
|
|
if json_name in res {
|
|
typ.$(field.name) = ?i32(res[json_name]!.i64())
|
|
}
|
|
} $else $if field.typ is ?u64 {
|
|
if json_name in res {
|
|
typ.$(field.name) = ?u64(res[json_name]!.i64())
|
|
}
|
|
} $else $if field.typ is ?i64 {
|
|
if json_name in res {
|
|
typ.$(field.name) = ?i64(res[json_name]!.i64())
|
|
}
|
|
} $else $if field.typ is ?int {
|
|
if json_name in res {
|
|
typ.$(field.name) = ?int(res[json_name]!.i64())
|
|
}
|
|
} $else $if field.typ is f32 {
|
|
typ.$(field.name) = res[json_name]!.f32()
|
|
} $else $if field.typ is ?f32 {
|
|
if json_name in res {
|
|
typ.$(field.name) = res[json_name]!.f32()
|
|
}
|
|
} $else $if field.typ is f64 {
|
|
typ.$(field.name) = res[json_name]!.f64()
|
|
} $else $if field.typ is ?f64 {
|
|
if json_name in res {
|
|
typ.$(field.name) = res[json_name]!.f64()
|
|
}
|
|
} $else $if field.typ is bool {
|
|
typ.$(field.name) = res[json_name]!.bool()
|
|
} $else $if field.typ is ?bool {
|
|
if json_name in res {
|
|
typ.$(field.name) = res[json_name]!.bool()
|
|
}
|
|
} $else $if field.typ is string {
|
|
typ.$(field.name) = res[json_name]!.str()
|
|
} $else $if field.typ is ?string {
|
|
if json_name in res {
|
|
typ.$(field.name) = res[json_name]!.str()
|
|
}
|
|
} $else $if field.typ is time.Time {
|
|
typ.$(field.name) = res[json_name]!.to_time()!
|
|
} $else $if field.typ is ?time.Time {
|
|
if json_name in res {
|
|
typ.$(field.name) = res[json_name]!.to_time()!
|
|
}
|
|
} $else $if field.typ is Any {
|
|
typ.$(field.name) = res[json_name]!
|
|
} $else $if field.typ is ?Any {
|
|
if json_name in res {
|
|
typ.$(field.name) = res[json_name]!
|
|
}
|
|
} $else $if field.is_array {
|
|
arr := res[field.name]! as []Any
|
|
decode_array_item(mut typ.$(field.name), arr)
|
|
} $else $if field.is_struct {
|
|
typ.$(field.name) = decode_struct(typ.$(field.name), res[field.name]!.as_map())!
|
|
} $else $if field.is_alias {
|
|
} $else $if field.is_map {
|
|
typ.$(field.name) = decode_map(typ.$(field.name), res[field.name]!.as_map())!
|
|
} $else {
|
|
return error("The type of `${field.name}` can't be decoded. Please open an issue at https://github.com/vlang/v/issues/new/choose")
|
|
}
|
|
}
|
|
}
|
|
} $else $if T is $map {
|
|
for k, v in res {
|
|
// // TODO: make this work to decode types like `map[string]StructType[bool]`
|
|
// $if typeof(typ[k]).idx is string {
|
|
// typ[k] = v.str()
|
|
// } $else $if typeof(typ[k]).idx is $struct {
|
|
|
|
// }
|
|
match v {
|
|
string {
|
|
typ[k] = v.str()
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
} $else {
|
|
return error("The type `${T.name}` can't be decoded.")
|
|
}
|
|
return typ
|
|
}
|
|
|
|
fn decode_array_item[T](mut field T, arr []Any) {
|
|
// vfmt off
|
|
match typeof[T]().idx {
|
|
typeof[[]bool]().idx { field = arr.map(it.bool()) }
|
|
typeof[[]?bool]().idx { field = arr.map(?bool(it.bool())) }
|
|
typeof[[]f32]().idx { field = arr.map(it.f32()) }
|
|
typeof[[]?f32]().idx { field = arr.map(?f32(it.f32())) }
|
|
typeof[[]f64]().idx { field = arr.map(it.f64()) }
|
|
typeof[[]?f64]().idx { field = arr.map(?f64(it.f64())) }
|
|
typeof[[]i8]().idx { field = arr.map(it.i8()) }
|
|
typeof[[]?i8]().idx { field = arr.map(?i8(it.i8())) }
|
|
typeof[[]i16]().idx { field = arr.map(it.i16()) }
|
|
typeof[[]?i16]().idx { field = arr.map(?i16(it.i16())) }
|
|
typeof[[]i32]().idx { field = arr.map(it.i32()) }
|
|
typeof[[]?i32]().idx { field = arr.map(?i32(it.i32())) }
|
|
typeof[[]i64]().idx { field = arr.map(it.i64()) }
|
|
typeof[[]?i64]().idx { field = arr.map(?i64(it.i64())) }
|
|
typeof[[]int]().idx { field = arr.map(it.int()) }
|
|
typeof[[]?int]().idx { field = arr.map(?int(it.int())) }
|
|
typeof[[]string]().idx { field = arr.map(it.str()) }
|
|
typeof[[]?string]().idx { field = arr.map(?string(it.str())) }
|
|
// NOTE: Using `!` on `to_time()` inside the array method causes a builder error - 2024/04/01.
|
|
typeof[[]time.Time]().idx { field = arr.map(it.to_time() or { time.Time{} }) }
|
|
typeof[[]?time.Time]().idx { field = arr.map(?time.Time(it.to_time() or { time.Time{} })) }
|
|
typeof[[]Any]().idx { field = arr.clone() }
|
|
typeof[[]?Any]().idx { field = arr.map(it) }
|
|
typeof[[]u8]().idx { field = arr.map(it.u64()) }
|
|
typeof[[]?u8]().idx { field = arr.map(?u8(it.u64())) }
|
|
typeof[[]u16]().idx { field = arr.map(it.u64()) }
|
|
typeof[[]?u16]().idx { field = arr.map(?u16(it.u64())) }
|
|
typeof[[]u32]().idx { field = arr.map(it.u64()) }
|
|
typeof[[]?u32]().idx { field = arr.map(?u32(it.u64())) }
|
|
typeof[[]u64]().idx { field = arr.map(it.u64()) }
|
|
typeof[[]?u64]().idx { field = arr.map(?u64(it.u64())) }
|
|
else {
|
|
$if T is [][]f32 { field << arr.map(it.as_map().values().map(it.f32())) }
|
|
$else $if T is [][]?f32 { field << arr.map(it.as_map().values().map(?f32(it.f32()))) }
|
|
$else $if T is [][]f64 { field << arr.map(it.as_map().values().map(it.f64())) }
|
|
$else $if T is [][]?f64 { field << arr.map(it.as_map().values().map(?f64(it.f64()))) }
|
|
$else $if T is [][]i8 { field << arr.map(it.as_map().values().map(it.i8())) }
|
|
$else $if T is [][]?i8 { field << arr.map(it.as_map().values().map(?i8(it.i8()))) }
|
|
$else $if T is [][]i16 { field << arr.map(it.as_map().values().map(it.i16())) }
|
|
$else $if T is [][]?i16 { field << arr.map(it.as_map().values().map(?i16(it.i16()))) }
|
|
$else $if T is [][]i32 { field << arr.map(it.as_map().values().map(it.i32())) }
|
|
$else $if T is [][]?i32 { field << arr.map(it.as_map().values().map(?i32(it.i32()))) }
|
|
$else $if T is [][]i64 { field << arr.map(it.as_map().values().map(it.i64())) }
|
|
$else $if T is [][]?i64 { field << arr.map(it.as_map().values().map(?i64(it.i64()))) }
|
|
$else $if T is [][]u8 { field << arr.map(it.as_map().values().map(it.u8())) }
|
|
$else $if T is [][]?u8 { field << arr.map(it.as_map().values().map(?u8(it.u8()))) }
|
|
$else $if T is [][]u16 { field << arr.map(it.as_map().values().map(it.u16())) }
|
|
$else $if T is [][]?u16 { field << arr.map(it.as_map().values().map(?u16(it.u16()))) }
|
|
$else $if T is [][]u32 { field << arr.map(it.as_map().values().map(it.u32())) }
|
|
$else $if T is [][]?u32 { field << arr.map(it.as_map().values().map(?u32(it.u32()))) }
|
|
$else $if T is [][]u64 { field << arr.map(it.as_map().values().map(it.u64())) }
|
|
$else $if T is [][]?u64 { field << arr.map(it.as_map().values().map(?u64(it.u64()))) }
|
|
$else $if T is [][]bool { field << arr.map(it.as_map().values().map(it.bool())) }
|
|
$else $if T is [][]?bool { field << arr.map(it.as_map().values().map(?bool(it.bool()))) }
|
|
$else $if T is [][]string { field << arr.map(it.as_map().values().map(it.str())) }
|
|
$else $if T is [][]?string { field << arr.map(it.as_map().values().map(?string(it.str()))) }
|
|
}
|
|
}
|
|
// vfmt on
|
|
}
|
|
|
|
fn decode_map[K, V](_ map[K]V, res map[string]Any) !map[K]V {
|
|
mut ret := map[K]V{}
|
|
|
|
for k, v in res {
|
|
$if V is $struct {
|
|
ret[k] = decode_struct(V{}, res[k]!.as_map())!
|
|
} $else $if V is $map {
|
|
ret[k] = decode_map(V{}, res[k]!.as_map())!
|
|
} $else $if V is $sumtype {
|
|
ret[k] = decode_struct(V{}, res[k]!.as_map())!
|
|
} $else $if V is $string {
|
|
ret[k] = v.str()
|
|
} $else $if V is int {
|
|
ret[k] = v.int()
|
|
} $else $if V is i64 {
|
|
ret[k] = v.i64()
|
|
} $else $if V is u64 {
|
|
ret[k] = v.u64()
|
|
} $else $if V is i32 {
|
|
ret[k] = v.i32()
|
|
} $else $if V is u32 {
|
|
ret[k] = v.u32()
|
|
} $else $if V is i16 {
|
|
ret[k] = v.i16()
|
|
} $else $if V is u16 {
|
|
ret[k] = v.u16()
|
|
} $else $if V is i8 {
|
|
ret[k] = v.i8()
|
|
} $else $if V is u8 {
|
|
ret[k] = v.u8()
|
|
} $else $if V is bool {
|
|
ret[k] = v.bool()
|
|
} $else {
|
|
dump(v)
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
fn (mut p Parser) decode_value() !Any {
|
|
if p.n_level + 1 == 500 {
|
|
return DecodeError{
|
|
message: 'reached maximum nesting level of 500'
|
|
}
|
|
}
|
|
match p.tok.kind {
|
|
// `[`
|
|
.lsbr {
|
|
return p.decode_array()
|
|
}
|
|
// `{`
|
|
.lcbr {
|
|
return p.decode_object()
|
|
}
|
|
.int, .float {
|
|
tl := p.tok.lit.bytestr()
|
|
kind := p.tok.kind
|
|
p.next_with_err()!
|
|
if p.convert_type {
|
|
$if !nofloat ? {
|
|
if kind == .float {
|
|
return Any(tl.f64())
|
|
}
|
|
}
|
|
return Any(tl.i64())
|
|
}
|
|
return Any(tl)
|
|
}
|
|
.bool {
|
|
lit := p.tok.lit.bytestr()
|
|
p.next_with_err()!
|
|
if p.convert_type {
|
|
return Any(lit.bool())
|
|
}
|
|
return Any(lit)
|
|
}
|
|
.null {
|
|
p.next_with_err()!
|
|
if p.convert_type {
|
|
return Any(null)
|
|
}
|
|
return Any('null')
|
|
}
|
|
.str {
|
|
str := p.tok.lit.bytestr()
|
|
p.next_with_err()!
|
|
return Any(str)
|
|
}
|
|
else {
|
|
return InvalidTokenError{
|
|
token: p.tok
|
|
}
|
|
}
|
|
}
|
|
return InvalidTokenError{
|
|
token: p.tok
|
|
}
|
|
}
|
|
|
|
@[manualfree]
|
|
fn (mut p Parser) decode_array() !Any {
|
|
mut items := []Any{}
|
|
p.next_with_err()!
|
|
p.n_level++
|
|
// `]`
|
|
for p.tok.kind != .rsbr {
|
|
item := p.decode_value()!
|
|
items << item
|
|
if p.tok.kind == .comma {
|
|
p.next_with_err()!
|
|
if p.tok.kind == .rsbr {
|
|
return InvalidTokenError{
|
|
token: p.tok
|
|
}
|
|
}
|
|
} else if p.tok.kind != .rsbr {
|
|
return UnknownTokenError{
|
|
token: p.tok
|
|
kind: .array
|
|
}
|
|
}
|
|
}
|
|
p.next_with_err()!
|
|
p.n_level--
|
|
return Any(items)
|
|
}
|
|
|
|
@[deprecated_after: '2025-03-18']
|
|
fn (mut p Parser) decode_object() !Any {
|
|
mut fields := map[string]Any{}
|
|
p.next_with_err()!
|
|
p.n_level++
|
|
// `}`
|
|
for p.tok.kind != .rcbr {
|
|
// step 1 -> key
|
|
if p.tok.kind != .str {
|
|
return InvalidTokenError{
|
|
token: p.tok
|
|
expected: .str
|
|
}
|
|
}
|
|
|
|
cur_key := p.tok.lit.bytestr()
|
|
p.next_with_err()!
|
|
// step 2 -> colon separator
|
|
if p.tok.kind != .colon {
|
|
return InvalidTokenError{
|
|
token: p.tok
|
|
expected: .colon
|
|
}
|
|
}
|
|
|
|
p.next_with_err()!
|
|
// step 3 -> value
|
|
fields[cur_key] = p.decode_value()!
|
|
if p.tok.kind !in [.comma, .rcbr] {
|
|
return InvalidTokenError{
|
|
token: p.tok
|
|
expected: .comma
|
|
}
|
|
} else if p.tok.kind == .comma {
|
|
p.next_with_err()!
|
|
}
|
|
}
|
|
p.next_with_err()!
|
|
// step 4 -> eof (end)
|
|
p.n_level--
|
|
return Any(fields)
|
|
}
|