// 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})' } pub struct DecodeError { line int column int message string } // code returns the error code of DecodeError pub fn (err DecodeError) code() int { return 3 } // msg returns the message of the DecodeError pub fn (err DecodeError) msg() string { return format_message(err.message, err.line, err.column) } pub struct InvalidTokenError { DecodeError token Token expected TokenKind } // code returns the error code of the InvalidTokenError pub fn (err InvalidTokenError) code() int { return 2 } // msg returns the message of the InvalidTokenError pub fn (err InvalidTokenError) msg() string { footer_text := if err.expected != .none_ { ', expecting `${err.expected}`' } else { '' } return format_message('invalid token `${err.token.kind}`${footer_text}', err.token.line, err.token.full_col()) } pub struct UnknownTokenError { DecodeError token Token kind ValueKind = .unknown } // code returns the error code of the UnknownTokenError pub fn (err UnknownTokenError) code() int { return 1 } // msg returns the error message of the UnknownTokenError pub fn (err UnknownTokenError) msg() string { return format_message("unknown token '${err.token.lit}' when decoding ${err.kind}.", err.token.line, err.token.full_col()) } struct Parser { pub mut: scanner &Scanner = unsafe { nil } prev_tok Token tok Token next_tok Token n_level int convert_type bool = true } 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 } } // Decodes a JSON string into an `Any` type. Returns an option. pub fn raw_decode(src string) !Any { mut p := new_parser(src, true) return p.decode() } // Same with `raw_decode`, but skips the type conversion for certain types when decoding a certain value. pub fn fast_raw_decode(src string) !Any { mut p := new_parser(src, false) return p.decode() } // decode is a generic function that decodes a JSON string into the target type. pub fn decode[T](src string) !T { res := raw_decode(src)!.as_map() return decode_struct[T](T{}, res) } // 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('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]!.int()) } $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.is_array { } $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 { } $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 } // decode - decodes provided JSON pub fn (mut p Parser) decode() !Any { p.next() p.next_with_err()! fi := p.decode_value()! if p.tok.kind != .eof { return InvalidTokenError{ token: p.tok } } return fi } 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) } 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) }