module json2 import strconv import time import math import sync.stdatomic const null_in_string = 'null' const true_in_string = 'true' const false_in_string = 'false' const whitespace_chars = [` `, `\t`, `\n`, `\r`]! // ValueInfo represents the position and length of a value, such as string, number, array, object key, and object value in a JSON string. struct ValueInfo { mut: position int // The position of the value in the JSON string. value_kind ValueKind // The kind of the value. has_escape bool // Whether a string contains an escape sequence. length int // The length of the value in the JSON string. } struct DecoderFieldInfo { key_name string is_skip bool is_required bool is_raw bool } @[markused] struct StructFieldInfo { name string // name is the V name of the field, for error messages. json_name_ptr voidptr json_name_len int is_skip bool is_required bool is_raw bool } // Keep runtime attribute parsing outside the compile-time field loop. This is called only while // a struct type's field metadata cache is initialized. The cached pointers borrow attribute // bytes, so autofree must not release the loop's attribute strings. @[manualfree; noinline] fn struct_field_info(field_name string, attrs []string) StructFieldInfo { mut json_name_str := field_name.str mut json_name_len := field_name.len mut is_json_skip := false for i in 0 .. attrs.len { attr := &attrs[i] if start, end := json_attr_value_range(attr) { if end <= start { continue } if end == start + 1 && (*attr)[start] == `-` { is_json_skip = true break } json_name_str = unsafe { attr.str + start } json_name_len = end - start break } } return StructFieldInfo{ name: field_name json_name_ptr: voidptr(json_name_str) json_name_len: json_name_len is_skip: attrs.contains('skip') || is_json_skip is_required: attrs.contains('required') is_raw: attrs.contains('raw') } } struct StructKeyDecodeResult[T] { matched bool value T } // DecoderOptions provides options for JSON decoding. // By default, decoding is lenient. Use `strict: true` for strict type conversion // and `json5: true` to accept JSON5 syntax. @[markused; params] pub struct DecoderOptions { pub: // json5 accepts JSON5 comments, keys, strings, trailing commas and numbers. json5 bool // In strict mode, quoted strings are not accepted as numbers. // For example, '"123"' will fail to decode as int in strict mode, // but will succeed in default mode. // Strict mode also requires a fixed size array to get a JSON array with exactly // as many elements. By default, `null` or missing trailing elements keep their // default values, and extra elements are ignored. // Strict mode also rejects `null` for a value that is not an option. By default, // like in the removed `json` module, `null` is the zero value of a number, bool, // string, enum or time, an empty array or map, and a nested struct with its // default field values. strict bool } // DecodeBuffer retains token storage between decode_reuse calls. Give each // concurrent or reentrant decoding operation its own buffer. It never retains // the input or decoded values; its capacity grows with the largest token count. // Create independent buffers with DecodeBuffer{}; do not copy a buffer after // decoding has retained storage in it, including after a decoding error. pub struct DecodeBuffer { mut: values_info []ValueInfo } // Decoder is the internal decoding state. @[markused] struct Decoder { json string // json is the JSON data to be decoded. json5 bool source_json string // Original JSON5 input, used only for decoding diagnostics. source_values_info []Json5SourceSpan strict bool // strict mode rejects quoted strings as numbers, fixed arrays of another length, and null values mut: // Scratch result of check_string, copied to the corresponding ValueInfo. string_has_escape bool // values_info describes every value of the JSON string, in the order in which they // start: an array or an object is followed by its elements, or by its keys and their // values. It is one flat array, so nothing is allocated per value, and moving to // the next value is an index increment. values_info []ValueInfo checker_idx int // checker_idx is the current index of the decoder. current_idx int // current_idx is the index in values_info of the value that is decoded next. // checker_depth is the nesting depth reached by the current check_array/ // check_object call chain; see max_nesting_depth in check.v. checker_depth int } // no_value_idx is the index of a value that is not there, such as the `_type` of an // object without that key. const no_value_idx = -1 // has_value reports whether `value_idx` is the index of a value in values_info. The // index after the last value, and no_value_idx, are not. @[inline; markused] fn (decoder &Decoder) has_value(value_idx int) bool { return value_idx >= 0 && value_idx < decoder.values_info.len } // current_value returns the value that is decoded next. @[inline; markused] fn (decoder &Decoder) current_value() ValueInfo { return decoder.values_info[decoder.current_idx] } // skip_strings_min_json_len is the length of a JSON string from which // value_count_bound() skips the strings in it. That takes longer, which a shorter // JSON string is not worth: the room that it saves there is small. const skip_strings_min_json_len = 16384 // value_count_bound returns an upper bound of the number of values in the `json` // string, to allocate values_info once, with (nearly) no room to spare. Outside of // strings, every value but the root follows a `,`, a `:`, a `[` or a `{` of its own, // so the bound is only higher than the count for empty arrays and objects, and in a // short JSON string, for those characters in strings. @[direct_array_access; markused] fn value_count_bound(json string) int { mut count := 1 if json.len < skip_strings_min_json_len { for c in json { // `[` and `{` only differ in the bit that is set here. count += int(c == `,`) + int(c == `:`) + int((c | 0x20) == `{`) } return count } mut i := 0 for i < json.len { c := json[i] i++ if c == `"` { i = json_string_end(json, i) + 1 continue } count += int(c == `,`) + int(c == `:`) + int((c | 0x20) == `{`) } return count } // json_string_end returns the index of the `"` that ends the JSON string whose content // starts at `start` in `json`, or `json.len` when the string is not closed. @[direct_array_access] fn json_string_end(json string, start int) int { mut i := start for { // A long string is skipped 8 bytes at a time, as long as none is a `"` or a `\`. for i + 8 <= json.len { mut word := u64(0) unsafe { vmemcpy(&word, json.str + i, 8) } if word_has_byte(word, `"`) || word_has_byte(word, `\\`) { break } i += 8 } for i < json.len && json[i] != `"` && json[i] != `\\` { i++ } if i >= json.len { return json.len } if json[i] == `"` { return i } // The character after a `\` is escaped: a `"` there does not end the string. i += 2 } return json.len } // word_has_byte reports whether one of the 8 bytes of `word` is `b`. @[inline] fn word_has_byte(word u64, b u8) bool { // A byte of `diff` is zero where `word` has `b`. The subtraction sets the high bit // of a zero byte, that `~diff` keeps, and without a zero byte no high bit is left. diff := word ^ (u64(b) * 0x0101010101010101) return ((diff - 0x0101010101010101) & ~diff & 0x8080808080808080) != 0 } // add_value puts a value of `value_kind`, that starts at the checker's position, in // values_info. Its length is set once its end is known. @[direct_array_access; inline; markused] fn (mut checker Decoder) add_value(value_kind ValueKind) { if checker.values_info.len == checker.values_info.cap && !checker.values_info.flags.has(.managed) { capacity := value_count_bound(checker.json) unsafe { checker.values_info.grow_cap(capacity - checker.values_info.len) } } checker.values_info << ValueInfo{ position: checker.checker_idx value_kind: value_kind has_escape: false } if checker.values_info.flags.has(.managed) { unsafe { checker.values_info.flags |= .noslices } } } const max_context_length = 50 const max_extra_characters = 5 const tab_width = 8 pub struct JsonDecodeError { Error context string pub: message string line int character int } fn (e JsonDecodeError) msg() string { return '\n${e.line}:${e.character}: Invalid json: ${e.message}\n${e.context}' } // checker_error generates a checker error message showing the position in the json string @[markused] fn (mut checker Decoder) checker_error(message string) ! { position := checker.checker_idx mut line_number := 0 mut character_number := 0 mut last_newline := 0 for i := position - 1; i >= 0; i-- { if last_newline == 0 { if checker.json[i] == `\n` { last_newline = i + 1 } else if checker.json[i] == `\t` { character_number += tab_width } else { character_number++ } } if checker.json[i] == `\n` { line_number++ } } cutoff := character_number > max_context_length // either start of string, last newline or a limited amount of characters // Tabs increase display columns without adding bytes; never slice before the current line. context_start := if cutoff { int_max(last_newline, position - max_context_length) } else { last_newline } // print some extra characters mut context_end := int_min(checker.json.len, position + max_extra_characters) context_end_newline := checker.json[position..context_end].index_u8(`\n`) if context_end_newline > 0 { context_end = position + context_end_newline } mut context := '' if cutoff { context += '...' } context += checker.json[context_start..position] context += '\\e[31m${checker.json[position].ascii_str()}\\e[0m' context += checker.json[position + 1..context_end] context += '\n' if cutoff { context += ' '.repeat(max_context_length + 3) } else { context += ' '.repeat(character_number) } context += '\e[31m^\e[0m' return JsonDecodeError{ context: context message: 'Syntax: ${message}' line: line_number + 1 character: character_number + 1 } } // decode_error generates a decoding error from the decoding stage @[markused] fn (mut decoder Decoder) decode_error(message string) ! { mut error_info := ValueInfo{} mut error_idx := -1 if decoder.current_idx < decoder.values_info.len { error_idx = decoder.current_idx error_info = decoder.current_value() } else if decoder.values_info.len > 0 { error_idx = decoder.values_info.len - 1 error_info = decoder.values_info.last() } json := if decoder.json5 { decoder.source_json } else { decoder.json } if decoder.json5 && error_idx >= 0 { source_info := decoder.source_values_info[error_idx] error_info.position = source_info.position error_info.length = source_info.length } start := error_info.position if decoder.json5 { line, column, line_start := json5_source_location(json, start) return JsonDecodeError{ context: json5_decode_context(json, start, error_info.length, line_start, column) message: 'Data: ${message}' line: line + 1 character: column + 1 } } end := start + int_min(error_info.length, max_context_length) mut line_number := 0 mut character_number := 0 mut last_newline := 0 for i := start - 1; i >= 0; i-- { if last_newline == 0 { if json[i] == `\n` { last_newline = i + 1 } else if json[i] == `\t` { character_number += tab_width } else { character_number++ } } if json[i] == `\n` { line_number++ } } cutoff := character_number > max_context_length // either start of string, last newline or a limited amount of characters // Tabs increase display columns without adding bytes; never slice before the current line. context_start := if cutoff { int_max(last_newline, start - max_context_length) } else { last_newline } // print some extra characters mut context_end := int_min(json.len, end + max_extra_characters) context_end_newline := json[end..context_end].index_u8(`\n`) if context_end_newline != -1 { context_end = end + context_end_newline } mut context := '' if cutoff { context += '...' } context += json[context_start..start] context += '\\e[31m${json[start..end]}\\e[0m' context += json[end..context_end] context += '\n' if cutoff { context += ' '.repeat(max_context_length + 3) } else { context += ' '.repeat(character_number) } context += '\\e[31m${'~'.repeat(error_info.length)}\\e[0m' return JsonDecodeError{ context: context message: 'Data: ${message}' line: line_number + 1 character: character_number + 1 } } // decoder_for_text performs input validation and token allocation once in shared // code rather than specializing the same setup for every target type. @[manualfree] fn decoder_for_text(val string, params DecoderOptions) !Decoder { if val == '' { return JsonDecodeError{ message: 'empty string' line: 1 character: 1 } } mut decoder := Decoder{ json: val strict: params.strict values_info: []ValueInfo{cap: value_count_bound(val)} } decoder.check_json_format() or { unsafe { decoder.values_info.free() } return err } return decoder } // decode decodes a JSON string into a specified type. // By default, decoding is lenient. Use `strict: true` for strict type conversion // and `json5: true` to accept JSON5 syntax. // Use decode_standard to avoid generating JSON5 decoding code for the target type. // Input nested deeper than 512 array/object levels errors; the limit applies to // standard JSON only, the separate JSON5 parser is not covered by it. @[manualfree] pub fn decode[T](val string, params DecoderOptions) !T { if params.json5 { mut buffer := DecodeBuffer{} defer { unsafe { buffer.values_info.free() } } return decode_json5[T](val, mut buffer, params) } return decode_standard[T](val, params) } // decode_standard decodes standard JSON into a specified type without generating // JSON5 decoding code. It is lenient by default; use `strict: true` for strict // type conversion. The `json5` option is ignored; JSON5 syntax is always rejected. // Input nested deeper than 512 array/object levels errors. @[manualfree] pub fn decode_standard[T](val string, params DecoderOptions) !T { mut decoder := decoder_for_text(val, params)! // Nothing that is decoded refers to values_info, so it is released right away. defer { unsafe { decoder.values_info.free() decoder.source_values_info.free() } } return decoder.decode_root[T]() } // decode_reuse decodes with the same options and validation as decode, reusing // buffer's token allocation and growing it during validation instead of counting // values in a separate pass. Returned values remain valid when buffer is reused, // including after an error. The caller must provide exclusive access to buffer. // Input nested deeper than 512 array/object levels errors; the limit applies to // standard JSON only, the separate JSON5 parser is not covered by it. @[manualfree] pub fn decode_reuse[T](val string, mut buffer DecodeBuffer, params DecoderOptions) !T { if params.json5 { return decode_json5[T](val, mut buffer, params) } if val == '' { return JsonDecodeError{ message: 'empty string' line: 1 character: 1 } } mut decoder := Decoder{ json: val strict: params.strict // The decoder has exclusive access until the defer returns its storage. values_info: unsafe { buffer.values_info } } defer { buffer.values_info = unsafe { decoder.values_info } unsafe { decoder.source_values_info.free() } } decoder.values_info.clear() decoder.check_json_format()! return decoder.decode_root[T]() } fn (mut decoder Decoder) decode_root[T]() !T { mut result := T{} $if T.unaliased_typ is $array_dynamic { result.clear() decoder.decode_array(mut result)! } $else $if T.unaliased_typ is $map { decoder.decode_map(mut result)! } $else $if T is $pointer { // `&T`, `&&T` and `&&&T` point to a newly decoded value; `null` keeps `result`. decoder.decode_element[T](mut result)! } $else { decoder.decode_value(mut result)! } return result } // decode_new_pointer decodes the current value into newly allocated memory, behind // as many pointers as `P` has (`&T`, `&&T`, `&&&&T`, ...). fn (mut decoder Decoder) decode_new_pointer[P](_ P) !P { $if P.indirections == 1 { mut decoded_ptr := $new(P.pointee_type) decoder.decode_value(mut decoded_ptr)! return decoded_ptr } $else { return new_pointer_to(decoder.decode_new_pointer($zero(P.pointee_type))!) } } // new_pointer_to returns a new heap pointer to `value`: one more level of // indirection, for decoding `&&T`, `&&&T`, ... values. fn new_pointer_to[T](value T) &T { mut ptr := unsafe { &T(vcalloc(int(sizeof(T)))) } unsafe { *ptr = value } return ptr } // option_element_is_none reports whether the current array element is `none` for an // option element type: `null`, or `{}` (how the removed module wrote a `none` element // of a sum type's array) when the payload does not take an object, as for `?int`. fn (mut decoder Decoder) option_element_is_none[P](element ?P) bool { return decoder.current_value().value_kind == .null || (decoder.is_empty_object(decoder.current_idx) && option_payload_fit(element, .object) == 0) } // decode_option_payload decodes the current value as the payload of an option. fn (mut decoder Decoder) decode_option_payload[P](_ ?P) !P { $if P is $pointer { return decoder.decode_array_element(P{})! } $else { mut payload := P{} decoder.decode_value(mut payload)! return payload } } fn create_decoded_ptr[T](_ &T) &T { $if T is $interface { return unsafe { nil } } $else $if T.unaliased_typ is voidptr { return unsafe { nil } } $else { // Typed initialization retains struct defaults and container element metadata. return $new(T) } } fn decoder_field_infos[T]() []DecoderFieldInfo { mut field_infos := []DecoderFieldInfo{} $for field in T.fields { mut key_name := field.name mut is_json_skip := false for attr in field.attrs { if start, end := json_attr_value_range(attr) { if end <= start { continue } if end == start + 1 && (*attr)[start] == `-` { is_json_skip = true break } key_name = attr[start..end] break } } field_infos << DecoderFieldInfo{ key_name: key_name is_skip: field.attrs.contains('skip') || is_json_skip is_required: field.attrs.contains('required') is_raw: field.attrs.contains('raw') } } return field_infos } struct StructFieldInfoCache { mut: field_infos []StructFieldInfo } @[manualfree; unsafe] fn (mut decoder Decoder) cached_struct_field_infos[T]() &StructFieldInfoCache { static cache := &StructFieldInfoCache(nil) static initializing := u64(0) static initialized := u64(0) // Publish immutable field metadata only after its initialization completes. if stdatomic.load_u64(&initialized) == 0 { if stdatomic.fetch_add_u64(&initializing, 1) == 0 { cache = &StructFieldInfoCache{} $for field in T.fields { cache.field_infos << struct_field_info(field.name, field.attrs) } stdatomic.store_u64(&initialized, 1) } else { for stdatomic.load_u64(&initialized) == 0 { time.sleep(time.microsecond) } } } return cache } @[inline; markused] fn struct_field_is_decoded(decoded_mask u64, decoded_fields []bool, field_idx int) bool { if field_idx < 64 { return (decoded_mask & (u64(1) << u64(field_idx))) != 0 } return decoded_fields[field_idx] } @[inline; markused] fn mark_struct_field_decoded(decoded_mask u64, mut decoded_fields []bool, field_idx int) u64 { if field_idx < 64 { return decoded_mask | (u64(1) << u64(field_idx)) } decoded_fields[field_idx] = true return decoded_mask } // find_struct_field centralizes the runtime part of struct key matching. Keeping // this loop outside the comptime field loop avoids emitting the same skip, // length, and memory-comparison checks once for every struct field. @[noinline] fn (decoder &Decoder) find_struct_field(field_infos []StructFieldInfo, key_ptr voidptr, key_len int) int { for field_idx in 0 .. field_infos.len { field_info := &field_infos[field_idx] // `@[omitempty]` only affects encoding: an explicit empty value (`0`, `""`) is // decoded like any other, as in the removed `json` module. field_can_match := !field_info.is_skip || field_info.is_required field_name_matches := key_len == field_info.json_name_len && unsafe { vmemcmp(key_ptr, field_info.json_name_ptr, field_info.json_name_len) == 0 } if field_can_match && field_name_matches { return field_idx } } return -1 } // key_has_escape reports whether the JSON string key described by `key_info` // contains a `\` escape sequence. @[direct_array_access; inline] fn (decoder &Decoder) key_has_escape(key_info ValueInfo) bool { for i in key_info.position + 1 .. key_info.position + key_info.length - 1 { if decoder.json[i] == `\\` { return true } } return false } @[inline] fn (mut decoder Decoder) json_key_matches(key_info ValueInfo, key_name string) !bool { if decoder.key_has_escape(key_info) { return decoder.decode_string_value(key_info)! == key_name } if key_info.length - 2 != key_name.len { return false } return unsafe { vmemcmp(decoder.json.str + key_info.position + 1, key_name.str, key_name.len) == 0 } } // skip_current_value moves past the current value, and past the values nested in it: // those follow it in values_info, and start before its end. @[direct_array_access; markused] fn (decoder &Decoder) next_value(index int) int { if index >= decoder.values_info.len { return decoder.values_info.len } value := decoder.values_info[index] end := value.position + value.length mut next := index + 1 mut limit := decoder.values_info.len if next == limit || decoder.values_info[next].position >= end { return next } for next < limit { middle := next + (limit - next) / 2 if decoder.values_info[middle].position < end { next = middle + 1 } else { limit = middle } } return next } fn (decoder &Decoder) container_len(index int) int { value := decoder.values_info[index] end := value.position + value.length mut next := index + 1 mut count := 0 for next < decoder.values_info.len { if decoder.values_info[next].position >= end { break } count++ next = decoder.next_value(next) } return if value.value_kind == .object { count / 2 } else { count } } @[markused] fn (mut decoder Decoder) skip_current_value() { decoder.current_idx = decoder.next_value(decoder.current_idx) } @[manualfree] fn decode_struct_key[T](mut decoder Decoder, val T, key_info ValueInfo, prefix string, mut seen_required []string) !StructKeyDecodeResult[T] { field_infos := decoder_field_infos[T]() mut new_val := val mut i := 0 $for field in T.fields { field_info := field_infos[i] $if !field.is_embed { if decoder.json_key_matches(key_info, field_info.key_name)! || (prefix != '' && decoder.json_key_matches(key_info, prefix + field_info.key_name)!) { decoder.current_idx++ if field_info.is_skip { if field_info.is_required { seen_required << prefix + field.name } decoder.skip_current_value() return StructKeyDecodeResult[T]{ matched: true value: new_val } } if !field.attrs.contains('skip') { if field_info.is_required { seen_required << prefix + field.name } $if field.is_shared { decoder.reject_shared_struct_field(field_info.is_raw, field_info.is_required, field.name)! } $else $if field.typ is ?rune { decoder.decode_option_rune_struct_field(&new_val.$(field.name), field_info.is_raw)! } $else { decoder.decode_struct_field(&new_val.$(field.name), field_info.is_raw, field_info.is_required, field.name)! } return StructKeyDecodeResult[T]{ matched: true value: new_val } } } } i++ } i = 0 $for field in T.fields { field_info := field_infos[i] $if field.is_embed { if decoder.json_key_matches(key_info, field_info.key_name)! && decoder.has_value(decoder.current_idx + 1) && decoder.values_info[decoder.current_idx + 1].value_kind == .object { if field_info.is_required { seen_required << prefix + field.name } decoder.current_idx++ decoder.decode_value(mut new_val.$(field.name))! return StructKeyDecodeResult[T]{ matched: true value: new_val } } { embed_result := decode_struct_key(mut decoder, new_val.$(field.name), key_info, prefix + field.name + '.', mut seen_required)! if embed_result.matched { new_val.$(field.name) = embed_result.value return StructKeyDecodeResult[T]{ matched: true value: new_val } } } } i++ } return StructKeyDecodeResult[T]{ matched: false value: val } } fn check_required_struct_fields[T](mut decoder Decoder, val T, seen_required []string, prefix string) ! { field_infos := decoder_field_infos[T]() mut i := 0 $for field in T.fields { field_info := field_infos[i] if field_info.is_required && prefix + field.name !in seen_required { decoder.decode_error('missing required field `${field.name}`')! } $if field.is_embed { check_required_struct_fields(mut decoder, val.$(field.name), seen_required, prefix + field.name + '.')! } i++ } } // decode_struct_with_embeds decodes a JSON object into a struct with embedded structs, // whose fields can also be matched through those embeds. @[manualfree] fn (mut decoder Decoder) decode_struct_with_embeds[T](mut val T, struct_info ValueInfo) ! { struct_end := struct_info.position + struct_info.length decoder.current_idx++ mut seen_required := []string{} // json object loop for { if decoder.current_idx >= decoder.values_info.len { break } key_info := decoder.current_value() if key_info.position >= struct_end { break } decode_result := decode_struct_key(mut decoder, val, key_info, '', mut seen_required)! if decode_result.matched { val = decode_result.value } else { // The key doesn't match any field in the struct, skip the entire value // including all nested objects/arrays. decoder.current_idx++ decoder.skip_current_value() } } check_required_struct_fields(mut decoder, val, seen_required, '')! } // decode_struct_fields decodes a JSON object into a struct without embedded structs. // Everything that does not depend on the field types is done by non-generic helpers, and // the field values by decode_struct_field, which is specialized per field type and shared // by all structs. That keeps the code generated for each struct small. @[manualfree] fn (mut decoder Decoder) decode_struct_fields[T](mut val T, struct_info ValueInfo) ! { field_info_cache := unsafe { decoder.cached_struct_field_infos[T]() } mut state := struct_decode_state(field_info_cache.field_infos, struct_info.position + struct_info.length) decoder.current_idx++ // json object loop for { field_idx := decoder.next_decoded_struct_field(mut state)! if field_idx == struct_key_object_end { break } field_info := field_info_cache.field_infos[field_idx] mut i := 0 $for field in T.fields { if field.attrs.contains('skip') { // Handled above, without decoding (or even compiling a decoder for) its type. } else if i == field_idx { $if field.is_shared { decoder.reject_shared_struct_field(field_info.is_raw, field_info.is_required, field.name)! } $else $if field.typ is ?rune { decoder.decode_option_rune_struct_field(&val.$(field.name), field_info.is_raw)! } $else { decoder.decode_struct_field(&val.$(field.name), field_info.is_raw, field_info.is_required, field.name)! } } i++ } } decoder.check_required_struct_fields_decoded(field_info_cache.field_infos, state.decoded_mask, state.decoded_fields)! } struct StructDecodeState { field_infos []StructFieldInfo // Borrowed from the immutable per-type cache. struct_end int mut: decoded_mask u64 decoded_fields []bool } @[manualfree] fn struct_decode_state(field_infos []StructFieldInfo, struct_end int) StructDecodeState { mut state := StructDecodeState{ field_infos: field_infos, struct_end: struct_end } if field_infos.len > 64 { state.decoded_fields = []bool{len: field_infos.len} } return state } // next_decoded_struct_field shares key skipping and required-field tracking among // all struct types. It returns only fields that need a typed value decoder. @[manualfree] fn (mut decoder Decoder) next_decoded_struct_field(mut state StructDecodeState) !int { for { field_idx := decoder.next_struct_field_idx(state.field_infos, state.struct_end)! if field_idx == struct_key_object_end { return field_idx } if field_idx == struct_key_no_field { continue } state.decoded_mask = mark_struct_field_decoded(state.decoded_mask, mut state.decoded_fields, field_idx) if state.field_infos[field_idx].is_skip { // Preserve the existing decode behavior for `skip`+`required`. decoder.current_idx++ continue } return field_idx } return struct_key_object_end } const struct_key_no_field = -1 const struct_key_object_end = -2 // next_struct_field_idx reads the next key of the JSON object that ends at `struct_end`, // and returns the index of the struct field that it names, with the decoder at the field // value. A key that names no field is skipped with its value, and gives // `struct_key_no_field`. The end of the object gives `struct_key_object_end`. @[manualfree] fn (mut decoder Decoder) next_struct_field_idx(field_infos []StructFieldInfo, struct_end int) !int { if decoder.current_idx >= decoder.values_info.len { return struct_key_object_end } key_info := decoder.current_value() if key_info.position >= struct_end { return struct_key_object_end } mut key_ptr := unsafe { voidptr(decoder.json.str + key_info.position + 1) } mut key_len := key_info.length - 2 mut unescaped_key := '' if decoder.key_has_escape(key_info) { unescaped_key = decoder.decode_string_value(key_info)! key_ptr = voidptr(unescaped_key.str) key_len = unescaped_key.len } field_idx := decoder.find_struct_field(field_infos, key_ptr, key_len) // value node decoder.current_idx++ if field_idx < 0 { // The key doesn't match any field in the struct, skip the entire value // including all nested objects/arrays. decoder.skip_current_value() return struct_key_no_field } return field_idx } // check_required_struct_fields_decoded reports a required field that got no value. fn (mut decoder Decoder) check_required_struct_fields_decoded(field_infos []StructFieldInfo, decoded_mask u64, decoded_fields []bool) ! { for field_idx in 0 .. field_infos.len { field_info := &field_infos[field_idx] if field_info.is_required && !struct_field_is_decoded(decoded_mask, decoded_fields, field_idx) { decoder.decode_error('missing required field `${field_info.name}`')! } } } // decode_struct_field decodes the current JSON value into the struct field at `field`. // `field_name` is the V name of the field, for error messages. @[manualfree] fn (mut decoder Decoder) decode_struct_field[F](field &F, is_raw bool, is_required bool, field_name string) ! { if is_raw { $if F.unaliased_typ is $enum { decoder.decode_error('`raw` attribute cannot be used with enum fields')! } $else $if F is ?string || F is string { unsafe { *field = decoder.decode_raw_value() } } $else { decoder.decode_error('`raw` attribute can only be used with string fields')! } return } value_kind := decoder.current_value().value_kind $if F is $option { if value_kind == .null { unsafe { *field = none } decoder.current_idx++ } else { unsafe { *field = decoder.decode_option_payload(F(none))! } } } $else { if value_kind == .null { // A `@[required]` field needs a value: `null` is rejected like in the removed // `json` module, before the value decoders treat it leniently. if is_required { decoder.decode_error('required field `${field_name}` cannot be null')! } $if F.unaliased_typ is $array_dynamic || F.unaliased_typ is $map { mut target := unsafe { field } target.clear() decoder.skip_current_value() return } $else $if F.unaliased_typ is string { unsafe { *field = F('') } decoder.skip_current_value() return } $else $if F.indirections != 0 { unsafe { *field = nil } decoder.current_idx++ return } } $if F.indirections == 1 { mut decoded_ptr := create_decoded_ptr(*field) decoder.decode_value(mut decoded_ptr)! unsafe { *field = decoded_ptr } } $else $if F.indirections > 1 { unsafe { *field = decoder.decode_new_pointer(*field)! } } $else { mut target := unsafe { field } decoder.decode_value(mut target)! } } } // decode_option_rune_struct_field decodes a `?rune` struct field. The payload of a generic // `?rune` does not keep its `rune` type, so decode_struct_field would decode a number. fn (mut decoder Decoder) decode_option_rune_struct_field(field &?rune, is_raw bool) ! { if is_raw { decoder.decode_error('`raw` attribute can only be used with string fields')! } if decoder.current_value().value_kind == .null { unsafe { *field = none } decoder.current_idx++ return } mut unwrapped_rune := rune(0) decoder.decode_value(mut unwrapped_rune)! unsafe { *field = unwrapped_rune } } // reject_shared_struct_field reports the error for a value of a `shared` struct field, // which cannot be decoded. fn (mut decoder Decoder) reject_shared_struct_field(is_raw bool, is_required bool, field_name string) ! { if is_raw { decoder.decode_error('`raw` attribute can only be used with string fields')! } if is_required && decoder.current_value().value_kind == .null { decoder.decode_error('required field `${field_name}` cannot be null')! } decoder.decode_error('shared fields cannot be decoded')! } // decode_raw_value returns the JSON text of the current value, for a `@[raw]` field, and // moves past it. fn (mut decoder Decoder) decode_raw_value() string { value_info := decoder.current_value() raw := decoder.retained_text(value_info.position, value_info.position + value_info.length) decoder.skip_current_value() return raw } // retained_text copies text that may outlive the decoder's input buffer. @[manualfree] fn (decoder &Decoder) retained_text(start int, end int) string { return decoder.json.substr(start, end) } // decode_value decodes a value from the JSON nodes. @[manualfree] fn (mut decoder Decoder) decode_value[T](mut val T) ! { $if T.unaliased_typ is voidptr { // skip voidptr fields - they cannot be decoded from JSON decoder.current_idx++ return } $else $if T is $interface { // skip interface fields - they cannot be decoded from JSON decoder.current_idx++ return } $else $if T is $option { // An option element of an array or map (`[]?int`): `null` is `none`, anything // else is the payload, like in the removed `json` module. if decoder.current_value().value_kind == .null { val = none decoder.current_idx++ return } val = decoder.decode_option_payload(val)! return } $else { // Custom Decoders $if val is ValueDecoder { mut value := Any(null) decoder.decode_sumtype(mut value)! val.from_json(value) return } $if val is StringDecoder { struct_info := decoder.current_value() if struct_info.value_kind == .string { val.from_json_string(decoder.retained_text(struct_info.position + 1, struct_info.position + struct_info.length - 1)) or { decoder.decode_error('${typeof(val).name}: ${err.msg()}')! } decoder.current_idx++ return } } $if val is NumberDecoder { struct_info := decoder.current_value() if struct_info.value_kind == .number { val.from_json_number(decoder.retained_text(struct_info.position, struct_info.position + struct_info.length)) or { decoder.decode_error('${typeof(val).name}: ${err.msg()}')! } decoder.current_idx++ return } } $if val is BooleanDecoder { struct_info := decoder.current_value() if struct_info.value_kind == .boolean { val.from_json_boolean(decoder.json[struct_info.position] == `t`) decoder.current_idx++ return } } $if val is NullDecoder { struct_info := decoder.current_value() if struct_info.value_kind == .null { val.from_json_null() decoder.current_idx++ return } } $if T.unaliased_typ is $int || T.unaliased_typ is $float || T.unaliased_typ is bool || T.unaliased_typ is string || T.unaliased_typ is $enum { if decoder.current_value().value_kind == .null && !decoder.strict { // Outside of strict mode, `null` is the zero value, like in the removed // `json` module: `[1, null]` decodes into `[]int` as `[1, 0]`. val = $zero(T) decoder.current_idx++ return } } $if T.unaliased_typ is voidptr { // skip voidptr fields - they cannot be decoded from JSON decoder.current_idx++ return } $else $if T.unaliased_typ is string { value_info := decoder.current_value() if (value_info.value_kind == .object || value_info.value_kind == .array) && decoder.current_idx != 0 { // Like a string field (and the removed `json` module), an object or array // decodes into a string as its JSON text, also as an element or map value. // The removed module had no string root, which stays an error. val = T(decoder.retained_text(value_info.position, value_info.position + value_info.length)) decoder.skip_current_value() return } decoder.decode_string(mut val)! } $else $if T.unaliased_typ is time.Time { value_info := decoder.current_value() mut decoded_time := time.Time{} if value_info.value_kind == .string { decoded_time.from_json_string(decoder.json[value_info.position + 1..value_info.position + value_info.length - 1]) or { decoder.decode_error('${typeof(val).name}: ${err.msg()}')! } } else if value_info.value_kind == .number { decoded_time.from_json_number(decoder.json[value_info.position..value_info.position + value_info.length]) or { decoder.decode_error('${typeof(val).name}: ${err.msg()}')! } } else if value_info.value_kind == .null && !decoder.strict { // Outside of strict mode `null` is the zero time, like in the removed module. } else if value_info.value_kind == .object { // The removed module wrote a time in a sum type, also as an element of an // array variant or an option payload, as `{"_type":"Time","value":...}`. // Only that wrapper is a time: other objects are rejected, like before. type_field_idx := decoder.get_sumtype_type_field_idx(decoder.current_idx) if !decoder.sumtype_type_field_matches(type_field_idx, 'Time') && !decoder.sumtype_type_field_matches(type_field_idx, sumtype_variant_name(T.name)) { decoder.decode_error('Expected string, number or `Time` object, but got another object')! } decoder.decode_sumtype_time(mut decoded_time)! val = T(decoded_time) return } else { decoder.decode_error('Expected string or number, but got ${value_info.value_kind}')! } val = T(decoded_time) } $else $if T.unaliased_typ is $sumtype { decoder.decode_sumtype(mut val)! return } $else $if T.unaliased_typ is $map { decoder.decode_map(mut val)! return } $else $if T.unaliased_typ is $array_dynamic { val.clear() decoder.decode_array(mut val)! // return to avoid the next increment of the current node // this is because the current node is already incremented in the decode_array function // remove this line will cause the current node to be incremented twice // and bug recursive array decoding like `[][]int{}` return } $else $if T.unaliased_typ is $array_fixed { decoder.decode_fixed_array(mut val)! return } $else $if T.unaliased_typ is $struct { struct_info := decoder.current_value() if struct_info.value_kind == .object { // Only a struct with an embedded struct needs its keys matched through its // embeds too. Deciding that at compile time keeps the larger embed path out // of the code generated for every other struct. $for field in T.fields { $if field.is_embed { decoder.decode_struct_with_embeds(mut val, struct_info)! return } } decoder.decode_struct_fields(mut val, struct_info)! } else if struct_info.value_kind == .null && !decoder.strict && decoder.current_idx != 0 { // Outside of strict mode a nested `null` is the struct with its default // field values, like in the removed `json` module, which rejected a // `null` root. val = T{} decoder.current_idx++ } else { decoder.decode_error('Expected object, but got ${struct_info.value_kind}')! } return } $else $if T.unaliased_typ is bool { value_info := decoder.current_value() if value_info.value_kind != .boolean { decoder.decode_error('Expected boolean, but got ${value_info.value_kind}')! } unsafe { val = vmemcmp(decoder.json.str + value_info.position, c'true', 'true'.len) == 0 } } $else $if T.unaliased_typ is rune { // Like the removed `json` module, a rune is decoded from a JSON string (its // first character); a number is taken as the code point. value_info := decoder.current_value() if value_info.value_kind == .string { decoded := decoder.decode_string_value(value_info)! val = if decoded.len > 0 { T(decoded.runes()[0]) } else { T(0) } } else if value_info.value_kind == .number { mut code_point := u32(0) unsafe { decoder.decode_number(&code_point)! } val = T(code_point) } else { decoder.decode_error('Expected string, but got ${value_info.value_kind}')! } } $else $if T.unaliased_typ is $float || T.unaliased_typ is $int { value_info := decoder.current_value() if value_info.value_kind == .number { unsafe { decoder.decode_number(&val)! } } else if value_info.value_kind == .string && !decoder.strict { // In default mode, try to parse quoted strings as numbers val = decoder.decode_number_from_string[T]()! } else { decoder.decode_error('Expected number, but got ${value_info.value_kind}')! } } $else $if T.unaliased_typ is $enum { decoder.decode_enum(mut val)! } $else { decoder.decode_error('cannot decode value with ${typeof(val).name} type')! } decoder.current_idx++ } // $else (not voidptr / interface) } fn (mut decoder Decoder) decode_string[T](mut val T) ! { _ = val string_info := decoder.current_value() if string_info.value_kind == .string { val = decoder.decode_string_value(string_info)! } else { decoder.decode_error('Expected string, but got ${string_info.value_kind}')! } } // decode_string_value returns the unescaped content of the JSON string described by `string_info`. @[manualfree] fn (mut decoder Decoder) decode_string_value(string_info ValueInfo) !string { string_start := string_info.position + 1 string_end := string_info.position + string_info.length - 1 if !string_info.has_escape { return decoder.retained_text(string_start, string_end) } mut string_buffer := []u8{cap: string_info.length} // might be too long but most json strings don't contain many escape characters anyways defer { unsafe { string_buffer.free() } } mut buffer_index := 1 mut string_index := 1 for string_index < string_info.length - 1 { current_byte := decoder.json[string_info.position + string_index] if current_byte == `\\` { // push all characters up to this point unsafe { string_buffer.push_many(decoder.json.str + string_info.position + buffer_index, string_index - buffer_index) } string_index++ escaped_char := decoder.json[string_info.position + string_index] string_index++ match escaped_char { `/`, `"`, `\\` { string_buffer << escaped_char } `b` { string_buffer << `\b` } `f` { string_buffer << `\f` } `n` { string_buffer << `\n` } `r` { string_buffer << `\r` } `t` { string_buffer << `\t` } `u` { unicode_point := rune(strconv.parse_uint(decoder.json[string_info.position + string_index..string_info.position + string_index + 4], 16, 32)!) string_index += 4 if unicode_point < 0xD800 || unicode_point > 0xDFFF { // normal utf-8 string_buffer << unicode_point.bytes() } else if unicode_point >= 0xDC00 { // trail surrogate -> invalid decoder.decode_error('Got trail surrogate: ${u32(unicode_point):04X} before head surrogate.')! } else { // head surrogate -> treat as utf-16 if string_index > string_info.length - 6 { decoder.decode_error('Expected a trail surrogate after a head surrogate, but got no valid escape sequence.')! } if decoder.json[string_info.position + string_index..string_info.position + string_index + 2] != '\\u' { decoder.decode_error('Expected a trail surrogate after a head surrogate, but got no valid escape sequence.')! } string_index += 2 unicode_point2 := rune(strconv.parse_uint(decoder.json[string_info.position + string_index..string_info.position + string_index + 4], 16, 32)!) string_index += 4 if unicode_point2 < 0xDC00 { decoder.decode_error('Expected a trail surrogate after a head surrogate, but got ${u32(unicode_point):04X}.')! } final_unicode_point := (unicode_point2 & 0x3FF) + ((unicode_point & 0x3FF) << 10) + 0x10000 string_buffer << final_unicode_point.bytes() } } else {} } // has already been checked buffer_index = string_index } else { string_index++ } } // push the rest unsafe { string_buffer.push_many(decoder.json.str + string_info.position + buffer_index, string_index - buffer_index) } return string_buffer.bytestr() } fn (mut decoder Decoder) decode_array[T](mut val []T) ! { $if T is $interface || T.unaliased_typ is voidptr { decoder.skip_current_value() return } array_info := decoder.values_info[decoder.current_idx] if array_info.value_kind == .null && !decoder.strict { decoder.current_idx++ return } if array_info.value_kind != .array { return decoder.decode_error('Expected array, but got ${array_info.value_kind}') } capacity := decoder.container_len(decoder.current_idx) mut decoded := []T{cap: capacity} decoder.current_idx++ array_end := array_info.position + array_info.length for { if decoder.current_idx >= decoder.values_info.len || decoder.values_info[decoder.current_idx].position >= array_end { break } $if T is $option { // An option element (`[]?int`): `null` is `none`. Decoded directly, // since v3 cannot return `!?T` from the element helper. if decoder.option_element_is_none(T(none)) { decoded << T(none) decoder.skip_current_value() } else { decoded << decoder.decode_option_payload(T(none))! } } $else { mut element := T{} decoder.decode_element[T](mut element)! decoded << element } } val = decoded } fn (mut decoder Decoder) decode_element[E](mut element E) ! { $if E is $interface { decoder.skip_current_value() } $else $if E.unaliased_typ is voidptr { decoder.skip_current_value() } $else $if E.indirections == 1 { if decoder.values_info[decoder.current_idx].value_kind == .null { decoder.current_idx++ } else { mut decoded_ptr := create_decoded_ptr(element) decoder.decode_value(mut decoded_ptr)! element = decoded_ptr } } $else $if E.indirections > 1 { // `&&T`, `&&&T`, ... elements point to a newly decoded value, like fields. if decoder.values_info[decoder.current_idx].value_kind == .null { decoder.current_idx++ } else { element = decoder.decode_new_pointer(element)! } } $else { decoder.decode_value(mut element)! } } // decode_fixed_array decodes a JSON array into the elements of a fixed size array. // Outside of strict mode it behaves like the removed `json` module: `null` keeps the // default elements, a shorter array only replaces the leading elements, and extra // elements are skipped. fn (mut decoder Decoder) decode_fixed_array[T](mut val T) ! { array_info := decoder.current_value() if array_info.value_kind == .null && !decoder.strict { decoder.current_idx++ return } if array_info.value_kind != .array { decoder.decode_error('Expected array, but got ${array_info.value_kind}')! } decoder.current_idx++ array_end := array_info.position + array_info.length mut idx := 0 for decoder.current_idx < decoder.values_info.len && decoder.current_value().position < array_end { if idx < val.len { decoder.decode_fixed_array_element(&val[idx])! } else { decoder.skip_current_value() } idx++ } if decoder.strict && idx != val.len { decoder.decode_error('Fixed size array expected ${val.len} elements but got ${idx} elements')! } } // decode_fixed_array_element decodes the current JSON value into a fixed size array // element. Elements are decoded in place, since v3 cannot yet assign a fixed array // element that is itself a fixed array through a `mut` parameter; only a pointer // element is replaced. fn (mut decoder Decoder) decode_fixed_array_element[E](element &E) ! { $if E is $option { if decoder.option_element_is_none(E(none)) { unsafe { *element = E(none) } decoder.skip_current_value() } else { payload := decoder.decode_option_payload(E(none))! unsafe { *element = payload } } } $else $if E is $pointer { unsafe { *element = decoder.decode_array_element(*element)! } } $else { mut target := unsafe { element } decoder.decode_value(mut target)! } } // decode_array_element decodes the current JSON value as an array element that // starts out as `initial`. It takes and returns the element by value: for a `mut` // parameter, a pointer element type would be inferred without its `&`. fn (mut decoder Decoder) decode_array_element[E](initial E) !E { mut element := initial $if E is $interface { decoder.skip_current_value() } $else $if E.unaliased_typ is voidptr { decoder.skip_current_value() } $else $if E.indirections == 1 { if decoder.current_value().value_kind == .null { decoder.current_idx++ } else { mut decoded_ptr := create_decoded_ptr(element) decoder.decode_value(mut decoded_ptr)! element = decoded_ptr } } $else $if E.indirections > 1 { // `&&T`, `&&&T`, ... elements point to a newly decoded value, like fields. if decoder.current_value().value_kind == .null { decoder.current_idx++ } else { element = decoder.decode_new_pointer(element)! } } $else { decoder.decode_value(mut element)! } return element } // decode_enum_map_key accepts member names and the map encoder's flag-enum syntax. // JSON attributes only rename scalar enum values, not map keys. fn (mut decoder Decoder) decode_enum_map_key[K](key_str string) !K { $for member in K.values { if key_str == member.name { return K(member.value) } } zero := unsafe { K(0) } zero_str := '${zero}' // Flag enums, including aliases, stringify zero as `Enum{}`. Use the same // type prefix as the map encoder instead of assuming the alias's name. if zero_str.ends_with('{}') { prefix := zero_str[..zero_str.len - 1] if key_str.starts_with(prefix) && key_str.ends_with('}') { flags := key_str[prefix.len..key_str.len - 1].trim_space() if flags == '' { return zero } mut bits := u64(0) mut valid := true for flag in flags.split('|') { name := flag.trim_space() mut matched := false $for member in K.values { if name == '.${member.name}' { bits |= u64(member.value) matched = true } } if !matched { valid = false break } } if valid { return unsafe { K(bits) } } } } decoder.decode_error('String map key: `${key_str}` does not match any field in enum: ${K.name}')! return zero } fn (mut decoder Decoder) decode_map_key[K](info ValueInfo) !K { text := if decoder.key_has_escape(info) { decoder.decode_string_value(info)! } else { decoder.json.substr_unsafe(info.position + 1, info.position + info.length - 1) } $if K.unaliased_typ is string { return K(text) } $else $if K.unaliased_typ is $enum { return decoder.decode_enum_map_key[K](text)! } $else $if K.unaliased_typ is rune { mut runes := text.runes_iterator() first := runes.next() or { return error('expected one rune for JSON key') } if _ := runes.next() { return error('expected one rune for JSON key') } return K(first) } $else $if K.unaliased_typ is $int { return parse_integer_number[K](text)! } $else { return error('unsupported JSON map key type ${K.name}') } } fn (mut decoder Decoder) decode_map[K, V](mut val map[K]V) ! { $if V is $interface || V.unaliased_typ is voidptr { decoder.skip_current_value() return } map_info := decoder.values_info[decoder.current_idx] if map_info.value_kind == .null && !decoder.strict { val.clear() decoder.current_idx++ return } if map_info.value_kind != .object { return decoder.decode_error('Expected object, but got ${map_info.value_kind}') } map_end := map_info.position + map_info.length capacity := val.len + decoder.container_len(decoder.current_idx) val.reserve(u32(capacity)) decoder.current_idx++ for decoder.current_idx < decoder.values_info.len { key_info := decoder.values_info[decoder.current_idx] if key_info.position >= map_end { break } key := decoder.decode_map_key[K](key_info)! decoder.current_idx++ $if V is $option { if decoder.values_info[decoder.current_idx].value_kind == .null { val[key] = V(none) decoder.current_idx++ } else { val[key] = decoder.decode_option_payload(V(none))! } continue } mut map_value := V{} decoder.decode_element[V](mut map_value)! $if V.unaliased_typ is $map { val[key] = map_value.move() } $else { val[key] = map_value } } } fn (mut decoder Decoder) decode_enum[T](mut val T) ! { enum_info := decoder.current_value() if enum_info.value_kind == .number { $if T.unaliased_typ is $enum { if enum_uses_json_as_number[T]() { // Like the removed `json` module, a `@[json_as_number]` enum takes the // number as its backing value, declared or not (`Status(99)`). A // non-negative number is read as `u64`, so a `u64` backing value above // `max_i64` fits; a negative one as `i64`. if decoder.json[enum_info.position] == `-` { mut backing := i64(0) unsafe { decoder.decode_number(&backing)! } val = unsafe { T(backing) } } else { mut backing := u64(0) unsafe { decoder.decode_number(&backing)! } val = unsafe { T(backing) } } return } } mut result := 0 unsafe { decoder.decode_number(&result)! } $for value in T.values { if int(value.value) == result { val = value.value return } } decoder.decode_error('Number value: `${result}` does not match any field in enum: ${typeof(val).name}')! } else if enum_info.value_kind == .string { mut result := '' decoder.decode_string(mut result)! $for value in T.values { for attr in value.attrs { if json_attr := json_attr_value(attr) { if json_attr == result { val = value.value return } } } if value.name == result { val = value.value return } } decoder.decode_error('String value: `${result}` does not match any field in enum: ${typeof(val).name}')! } decoder.decode_error('Expected number or string value for enum, got: ${enum_info.value_kind}')! } const max_integer_number_digits = 20 @[markused] fn has_exponent_number_syntax(str string) bool { for c in str { if c == `e` || c == `E` { return true } } return false } @[markused] fn scientific_number_to_integer_string(str string) !string { if !has_exponent_number_syntax(str) { // Handle plain decimal numbers with zero fractional part (e.g., "-123.0") dot_pos := str.index_u8(`.`) if dot_pos >= 0 { frac := str[dot_pos + 1..] mut all_zeros := frac.len > 0 for c in frac { if c != `0` { all_zeros = false break } } if all_zeros { result := str[..dot_pos] if result.len == 0 || result == '-' || result == '+' { return '0' } return result } } return str } if str.len == 0 { return error('invalid scientific notation number') } mut i := 0 mut is_negative := false if str[i] == `+` || str[i] == `-` { is_negative = str[i] == `-` i++ } if i >= str.len { return error('invalid scientific notation number') } mut digits := []u8{cap: str.len} mut fractional_digits := 0 mut seen_digit := false mut seen_dot := false for i < str.len { c := str[i] if c >= `0` && c <= `9` { digits << c seen_digit = true if seen_dot { fractional_digits++ } i++ continue } if c == `.` && !seen_dot { seen_dot = true i++ continue } break } if !seen_digit || i >= str.len || (str[i] != `e` && str[i] != `E`) { return error('invalid scientific notation number') } i++ mut exponent_sign := 1 if i < str.len && (str[i] == `+` || str[i] == `-`) { if str[i] == `-` { exponent_sign = -1 } i++ } if i >= str.len || str[i] < `0` || str[i] > `9` { return error('invalid scientific notation number') } mut exponent := 0 exponent_cap := max_integer_number_digits + fractional_digits + 1 for i < str.len && str[i] >= `0` && str[i] <= `9` { if exponent < exponent_cap { exponent = (exponent * 10) + int(str[i] - `0`) } i++ } if i != str.len { return error('invalid scientific notation number') } if exponent_sign == -1 { exponent = -exponent } mut first_non_zero := 0 for first_non_zero < digits.len && digits[first_non_zero] == `0` { first_non_zero++ } if first_non_zero == digits.len { return '0' } digits = digits[first_non_zero..].clone() scale := exponent - fractional_digits if scale < 0 { truncated_digits := -scale if truncated_digits >= digits.len { return '0' } digits = digits[..digits.len - truncated_digits].clone() } else if scale > 0 { final_len := digits.len + scale if final_len > max_integer_number_digits { return error('number `${str}` exceeds 64-bit integer range') } mut out := []u8{cap: final_len + if is_negative { 1 } else { 0 }} if is_negative { out << `-` } out << digits for _ in 0 .. scale { out << `0` } return out.bytestr() } if digits.len == 0 { return '0' } mut out := []u8{cap: digits.len + if is_negative { 1 } else { 0 }} if is_negative { out << `-` } out << digits return out.bytestr() } fn parse_integer_number[T](str string) !T { int_str := scientific_number_to_integer_string(str)! $if T.unaliased_typ is i8 { return T(strconv.atoi8(int_str)!) } $else $if T.unaliased_typ is i16 { return T(strconv.atoi16(int_str)!) } $else $if T.unaliased_typ is i32 { return T(strconv.atoi32(int_str)!) } $else $if T.unaliased_typ is i64 { return T(strconv.atoi64(int_str)!) } $else $if T.unaliased_typ is u8 { return T(strconv.atou8(int_str)!) } $else $if T.unaliased_typ is u16 { return T(strconv.atou16(int_str)!) } $else $if T.unaliased_typ is u32 { return T(strconv.atou32(int_str)!) } $else $if T.unaliased_typ is u64 { return T(strconv.atou64(int_str)!) } $else $if T is int { return int(strconv.atoi64(int_str)!) } $else $if T.unaliased_typ is int || T.unaliased_typ is isize { return T(int(strconv.atoi64(int_str)!)) } $else $if T.unaliased_typ is uint { return T(uint(strconv.atou64(int_str)!)) } $else { return error('`parse_integer_number` cannot decode ${T.name} type') } } @[markused] fn parse_int_number(str string) !int { int_str := scientific_number_to_integer_string(str)! return int(strconv.atoi64(int_str)!) } fn parse_float_number[T](str string) !T { value := $if js { strconv.atof64(str)! } $else { strconv.atof64(str, allow_extra_chars: false)! } $if T.unaliased_typ is f32 { narrowed := f32(value) // Test the representation so finite-math optimizations cannot erase the guard. if math.f32_bits(narrowed) & u32(0x7fffffff) == u32(0x7f800000) { return error('number `${str}` exceeds ${T.name} range') } return T(narrowed) } $else $if T.unaliased_typ is f64 { if math.f64_bits(value) & u64(0x7fffffffffffffff) == u64(0x7ff0000000000000) { return error('number `${str}` exceeds ${T.name} range') } return T(value) } $else { return error('`parse_float_number` cannot decode ${T.name} type') } } // use pointer instead of mut so enum cast works @[manualfree; unsafe] fn (mut decoder Decoder) decode_number[T](val &T) ! { _ = val number_info := decoder.current_value() raw := decoder.json.substr_unsafe(number_info.position, number_info.position + number_info.length) $if T.unaliased_typ is $float { if decoder.json5 { *val = T(parse_json5_float(raw)!) return } } str := if decoder.json5 { normalize_json5_number(raw)! } else { raw } defer { if decoder.json5 { unsafe { str.free() } } } $match T.unaliased_typ { i8 { *val = parse_integer_number[T](str)! } i16 { *val = parse_integer_number[T](str)! } i32 { *val = parse_integer_number[T](str)! } i64 { *val = parse_integer_number[T](str)! } u8 { *val = parse_integer_number[T](str)! } u16 { *val = parse_integer_number[T](str)! } u32 { *val = parse_integer_number[T](str)! } u64 { *val = parse_integer_number[T](str)! } int { *val = parse_int_number(str)! } isize { *val = parse_integer_number[T](str)! } uint { *val = parse_integer_number[T](str)! } f32 { *val = parse_float_number[T](str)! } f64 { *val = parse_float_number[T](str)! } $else { return error('`decode_number` can not decode ${T.name} type') } } } // decode_number_from_string parses a number from a JSON string value (default mode). // This extracts the content between quotes and parses it as a number. @[manualfree] fn (mut decoder Decoder) decode_number_from_string[T]() !T { string_info := decoder.current_value() // Extract string content without quotes (position+1 to skip opening quote, length-2 to exclude both quotes) if string_info.length < 2 { return error('invalid string for number conversion') } str := decoder.json.substr_unsafe(string_info.position + 1, string_info.position + string_info.length - 1) $if T.unaliased_typ is i8 { return parse_integer_number[T](str)! } $else $if T.unaliased_typ is i16 { return parse_integer_number[T](str)! } $else $if T.unaliased_typ is i32 { return parse_integer_number[T](str)! } $else $if T.unaliased_typ is i64 { return parse_integer_number[T](str)! } $else $if T.unaliased_typ is u8 { return parse_integer_number[T](str)! } $else $if T.unaliased_typ is u16 { return parse_integer_number[T](str)! } $else $if T.unaliased_typ is u32 { return parse_integer_number[T](str)! } $else $if T.unaliased_typ is u64 { return parse_integer_number[T](str)! } $else $if T is int { return parse_int_number(str)! } $else $if T.unaliased_typ is int || T.unaliased_typ is isize { return parse_integer_number[T](str)! } $else $if T.unaliased_typ is uint { return parse_integer_number[T](str)! } $else $if T.unaliased_typ is f32 { return parse_float_number[T](str)! } $else $if T.unaliased_typ is f64 { return parse_float_number[T](str)! } $else { return error('`decode_number_from_string` cannot decode ${T.name} type') } }