json2: decode fn returns `?T`; add new tests (#6933)

pull/6213/head
Ned Palacios 2020-11-29 21:54:45 +08:00 committed by GitHub
parent 8f15af6adc
commit 6c634086b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 498 additions and 211 deletions

View File

@ -1,7 +1,7 @@
> `json2` was named just to avoid any unwanted potential conflicts with the existing codegen > The name `json2` was chosen to avoid any unwanted potential conflicts with the
> tailored for the main `json` module which is powered by CJSON. > existing codegen tailored for the main `json` module which is powered by CJSON.
An experimental version of the JSON parser written from scratch on V. `x.json2` is an experimental JSON parser written from scratch on V.
## Usage ## Usage
```v oksyntax ```v oksyntax
@ -29,7 +29,7 @@ fn main() {
mut arr := []json2.Any mut arr := []json2.Any
arr << 'rock' arr << 'rock'
arr << 'papers' arr << 'papers'
arr << json2.null() arr << json2.null
arr << 12 arr << 12
me['interests'] = arr me['interests'] = arr
@ -40,7 +40,12 @@ fn main() {
// Stringify to JSON // Stringify to JSON
println(me.str()) println(me.str())
//{"name":"Bob","age":18,"interests":["rock","papers","scissors",null,12],"pets":{"Sam":"Maltese"}} //{
// "name":"Bob",
// "age":18,
// "interests":["rock","papers","scissors",null,12],
// "pets":{"Sam":"Maltese"}
//}
// Encode a struct/type to JSON // Encode a struct/type to JSON
encoded_json := json2.encode<Person>(person2) encoded_json := json2.encode<Person>(person2)
@ -86,7 +91,7 @@ fn (p Person) to_json() string {
fn main() { fn main() {
resp := os.read_file('./person.json')? resp := os.read_file('./person.json')?
person := json2.decode<Person>(resp) person := json2.decode<Person>(resp)?
println(person) // Person{name: 'Bob', age: 28, pets: ['Floof']} println(person) // Person{name: 'Bob', age: 28, pets: ['Floof']}
person_json := json2.encode<Person>(person) person_json := json2.encode<Person>(person)
println(person_json) // {"name": "Bob", "age": 28, "pets": ["Floof"]} println(person_json) // {"name": "Bob", "age": 28, "pets": ["Floof"]}
@ -94,12 +99,36 @@ fn main() {
``` ```
## Using struct tags ## Using struct tags
`x.json2` cannot use struct tags just like when you use the `json` module. `x.json2` can access and use the struct field tags similar to the
However, it emits an `Any` type when decoding so it can be flexible on the way you use it. `json` module by using the comp-time `$for` for structs.
```v ignore
fn (mut p Person) from_json(f json2.Any) {
mp := an.as_map()
mut js_field_name := ''
$for field in Person.fields {
js_field_name = field.name
for attr in field.attrs {
if attr.starts_with('json:') {
js_field_name = attr.all_after('json:').trim_left(' ')
break
}
}
match field.name {
'name' { p.name = mp[js_field_name].str() }
'age' { u.age = mp[js_field_name].int() }
'pets' { u.pets = mp[js_field_name].arr().map(it.str()) }
else {}
}
}
}
```
### Null Values ### Null Values
`x.json2` have a `null` value for differentiating an undefined value and a null value. `x.json2` has a separate `null` type for differentiating an undefined value and a null value.
Use `is` for verifying the field you're using is a null. To verify that the field you're accessing is a `null`, use `<typ> is json2.Null`.
```v ignore ```v ignore
fn (mut p Person) from_json(f json2.Any) { fn (mut p Person) from_json(f json2.Any) {
@ -112,9 +141,8 @@ fn (mut p Person) from_json(f json2.Any) {
``` ```
### Custom field names ### Custom field names
In `json`, you can specify the field name you're mapping into the struct field by specifying Aside from using struct tags, you can also just simply cast the base field into a map (`as_map()`)
a `json:` tag. In `x.json2`, just simply cast the base field into a map (`as_map()`) and access the field you wish to put into the struct/type.
and get the value of the field you wish to put into the struct/type.
```v ignore ```v ignore
fn (mut p Person) from_json(f json2.Any) { fn (mut p Person) from_json(f json2.Any) {
@ -142,6 +170,6 @@ The following list shows the possible outputs when casting a value to an incompa
1. Casting non-array values as array (`arr()`) will return an array with the value as the content. 1. Casting non-array values as array (`arr()`) will return an array with the value as the content.
2. Casting non-map values as map (`as_map()`) will return a map with the value as the content. 2. Casting non-map values as map (`as_map()`) will return a map with the value as the content.
3. Casting non-string values to string (`str()`) 3. Casting non-string values to string (`str()`) will return the
will return the stringified representation of the value. JSON string representation of the value.
4. Casting non-numeric values to int/float (`int()`/`f64()`) will return zero. 4. Casting non-numeric values to int/float (`int()`/`i64()`/`f32()`/`f64()`) will return zero.

View File

@ -0,0 +1,131 @@
import x.json2
const (
sample_data = {
'int': json2.Any(int(1))
'i64': json2.Any(i64(128))
'f32': json2.Any(f32(2.0))
'f64': json2.Any(f64(1.283))
'bool': json2.Any(false)
'str': json2.Any('test')
'null': json2.Any(json2.null)
'arr': json2.Any([json2.Any('lol')])
'obj': json2.Any({
'foo': json2.Any(10)
})
}
)
fn is_null(f json2.Any) bool {
match f {
json2.Null { return true }
else { return false }
}
}
fn test_f32() {
// valid conversions
assert sample_data['int'].f32() == 1.0
assert sample_data['i64'].f32() == 128.0
assert sample_data['f32'].f32() == 2.0
assert sample_data['f64'].f32() == 1.2829999923706055
// invalid conversions
assert sample_data['bool'].f32() == 0.0
assert sample_data['str'].f32() == 0.0
assert sample_data['null'].f32() == 0.0
assert sample_data['arr'].f32() == 0.0
assert sample_data['obj'].f32() == 0.0
}
fn test_f64() {
// valid conversions
assert sample_data['int'].f64() == 1.0
assert sample_data['i64'].f64() == 128.0
assert sample_data['f32'].f64() == 2.0
assert sample_data['f64'].f64() == 1.283
// invalid conversions
assert sample_data['bool'].f64() == 0.0
assert sample_data['str'].f64() == 0.0
assert sample_data['null'].f64() == 0.0
assert sample_data['arr'].f64() == 0.0
assert sample_data['obj'].f64() == 0.0
}
fn test_int() {
// valid conversions
assert sample_data['int'].int() == 1
assert sample_data['i64'].int() == 128
assert sample_data['f32'].int() == 2
assert sample_data['f64'].int() == 1
assert json2.Any(true).int() == 1
// invalid conversions
assert json2.Any('123').int() == 0
assert sample_data['null'].int() == 0
assert sample_data['arr'].int() == 0
assert sample_data['obj'].int() == 0
}
fn test_i64() {
// valid conversions
assert sample_data['int'].i64() == 1
assert sample_data['i64'].i64() == 128
assert sample_data['f32'].i64() == 2
assert sample_data['f64'].i64() == 1
assert json2.Any(true).i64() == 1
// invalid conversions
assert json2.Any('123').i64() == 0
assert sample_data['null'].i64() == 0
assert sample_data['arr'].i64() == 0
assert sample_data['obj'].i64() == 0
}
fn test_as_map() {
assert sample_data['int'].as_map()['0'].int() == 1
assert sample_data['i64'].as_map()['0'].i64() == 128.0
assert sample_data['f32'].as_map()['0'].f32() == 2.0
assert sample_data['f64'].as_map()['0'].f64() == 1.283
assert sample_data['bool'].as_map()['0'].bool() == false
assert sample_data['str'].as_map()['0'].str() == 'test'
assert is_null(sample_data['null'].as_map()['0']) == true
assert sample_data['arr'].as_map()['0'].str() == 'lol'
assert sample_data['obj'].as_map()['foo'].int() == 10
}
fn test_arr() {
assert sample_data['int'].arr()[0].int() == 1
assert sample_data['i64'].arr()[0].i64() == 128.0
assert sample_data['f32'].arr()[0].f32() == 2.0
assert sample_data['f64'].arr()[0].f64() == 1.283
assert sample_data['bool'].arr()[0].bool() == false
assert sample_data['str'].arr()[0].str() == 'test'
assert is_null(sample_data['null'].arr()[0]) == true
assert sample_data['arr'].arr()[0].str() == 'lol'
assert sample_data['obj'].arr()[0].int() == 10
}
fn test_bool() {
// valid conversions
assert sample_data['bool'].bool() == false
assert json2.Any('true').bool() == true
// invalid conversions
assert sample_data['int'].bool() == false
assert sample_data['i64'].bool() == false
assert sample_data['f32'].bool() == false
assert sample_data['f64'].bool() == false
assert sample_data['null'].bool() == false
assert sample_data['arr'].bool() == false
assert sample_data['obj'].bool() == false
}
fn test_str() {
assert sample_data['int'].str() == '1'
assert sample_data['i64'].str() == '128'
assert sample_data['f32'].str() == '2.0'
assert sample_data['f64'].str() == '1.283'
assert sample_data['bool'].str() == 'false'
assert sample_data['str'].str() == 'test'
assert sample_data['null'].str() == 'null'
assert sample_data['arr'].str() == '["lol"]'
assert sample_data.str() ==
'{"int":1,"i64":128,"f32":2.0,"f64":1.283,"bool":false,"str":"test","null":null,"arr":["lol"],"obj":{"foo":10}}'
}

View File

@ -9,56 +9,69 @@ pub fn (mut obj map[string]Any) insert_str(key string, val string) {
fi = val fi = val
obj[key] = fi obj[key] = fi
} }
// Inserts an int into the map. // Inserts an int into the map.
pub fn (mut obj map[string]Any) insert_int(key string, val int) { pub fn (mut obj map[string]Any) insert_int(key string, val int) {
obj[key] = Any(val) obj[key] = Any(val)
} }
// Inserts a float into the map. // Inserts a float into the map.
pub fn (mut obj map[string]Any) insert_f(key string, val f64) { pub fn (mut obj map[string]Any) insert_f(key string, val f64) {
obj[key] = Any(val) obj[key] = Any(val)
} }
// Inserts a null into the map. // Inserts a null into the map.
pub fn (mut obj map[string]Any) insert_null(key string) { pub fn (mut obj map[string]Any) insert_null(key string) {
obj[key] = Any(Null{}) obj[key] = Any(Null{})
} }
// Inserts a bool into the map. // Inserts a bool into the map.
pub fn (mut obj map[string]Any) insert_bool(key string, val bool) { pub fn (mut obj map[string]Any) insert_bool(key string, val bool) {
obj[key] = Any(val) obj[key] = Any(val)
} }
// Inserts a map into the map. // Inserts a map into the map.
pub fn (mut obj map[string]Any) insert_map(key string, val map[string]Any) { pub fn (mut obj map[string]Any) insert_map(key string, val map[string]Any) {
obj[key] = Any(val) obj[key] = Any(val)
} }
// Inserts an array into the map. // Inserts an array into the map.
pub fn (mut obj map[string]Any) insert_arr(key string, val []Any) { pub fn (mut obj map[string]Any) insert_arr(key string, val []Any) {
obj[key] = Any(val) obj[key] = Any(val)
} }
// Inserts a string into the array. // Inserts a string into the array.
pub fn (mut arr []Any) insert_str(val string) { pub fn (mut arr []Any) insert_str(val string) {
mut fi := Any{} mut fi := Any{}
fi = val fi = val
arr << fi arr << fi
} }
// Inserts an int into the array. // Inserts an int into the array.
pub fn (mut arr []Any) insert_int(val int) { pub fn (mut arr []Any) insert_int(val int) {
arr << Any(val) arr << Any(val)
} }
// Inserts a float into the array. // Inserts a float into the array.
pub fn (mut arr []Any) insert_f(val f64) { pub fn (mut arr []Any) insert_f(val f64) {
arr << Any(val) arr << Any(val)
} }
// Inserts a null into the array. // Inserts a null into the array.
pub fn (mut arr []Any) insert_null() { pub fn (mut arr []Any) insert_null() {
arr << Any(Null{}) arr << Any(Null{})
} }
// Inserts a bool into the array. // Inserts a bool into the array.
pub fn (mut arr []Any) insert_bool(val bool) { pub fn (mut arr []Any) insert_bool(val bool) {
arr << Any(val) arr << Any(val)
} }
// Inserts a map into the array. // Inserts a map into the array.
pub fn (mut arr []Any) insert_map(val map[string]Any) { pub fn (mut arr []Any) insert_map(val map[string]Any) {
arr << Any(val) arr << Any(val)
} }
// Inserts an array into the array. // Inserts an array into the array.
pub fn (mut arr []Any) insert_arr(val []Any) { pub fn (mut arr []Any) insert_arr(val []Any) {
arr << Any(val) arr << Any(val)

View File

@ -12,32 +12,34 @@ import v.pref
// `Any` is a sum type that lists the possible types to be decoded and used. // `Any` is a sum type that lists the possible types to be decoded and used.
pub type Any = string | int | i64 | f32 | f64 | any_int | any_float | bool | Null | []Any | map[string]Any pub type Any = string | int | i64 | f32 | f64 | any_int | any_float | bool | Null | []Any | map[string]Any
// `Null` struct is a simple representation of the `null` value in JSON. // `Null` struct is a simple representation of the `null` value in JSON.
pub struct Null {} pub struct Null {
}
enum ParseMode { enum ParseMode {
array array
bool bool
invalid invalid
null null
number number
object object
string string
} }
const ( const (
formfeed_err = 'formfeed not allowed.' formfeed_err = 'formfeed not allowed.'
eof_err = 'reached eof. data not closed properly.' eof_err = 'reached eof. data not closed properly.'
) )
struct Parser { struct Parser {
mut: mut:
scanner &scanner.Scanner scanner &scanner.Scanner
p_tok token.Token p_tok token.Token
tok token.Token tok token.Token
n_tok token.Token n_tok token.Token
mode ParseMode = .invalid mode ParseMode = .invalid
n_level int n_level int
convert_type bool = true convert_type bool = true
} }
@ -79,7 +81,7 @@ fn new_parser(srce string, convert_type bool) Parser {
} }
} }
return Parser{ return Parser{
scanner: scanner.new_scanner(src, .parse_comments, &pref.Preferences{}), scanner: scanner.new_scanner(src, .parse_comments, &pref.Preferences{})
convert_type: convert_type convert_type: convert_type
} }
} }
@ -88,9 +90,10 @@ fn check_valid_hex(str string) ? {
if str.len != 4 { if str.len != 4 {
return error('hex string must be 4 characters.') return error('hex string must be 4 characters.')
} }
for l in str { for l in str {
if l.is_hex_digit() { continue } if l.is_hex_digit() {
continue
}
return error('provided string is not a hex digit.') return error('provided string is not a hex digit.')
} }
} }
@ -111,18 +114,16 @@ fn (mut p Parser) decode() ?Any {
fn (p Parser) is_formfeed() bool { fn (p Parser) is_formfeed() bool {
prev_tok_pos := p.p_tok.pos + p.p_tok.len - 2 prev_tok_pos := p.p_tok.pos + p.p_tok.len - 2
if prev_tok_pos < p.scanner.text.len && p.scanner.text[prev_tok_pos] == 0x0c { if prev_tok_pos < p.scanner.text.len && p.scanner.text[prev_tok_pos] == 0x0c {
return true return true
} }
return false return false
} }
fn (p Parser) is_singlequote() bool { fn (p Parser) is_singlequote() bool {
src := p.scanner.text src := p.scanner.text
prev_tok_pos := p.p_tok.pos + p.p_tok.len prev_tok_pos := p.p_tok.pos + p.p_tok.len
return src[prev_tok_pos] == `'` return src[prev_tok_pos] == `\'`
} }
fn (mut p Parser) detect_parse_mode() { fn (mut p Parser) detect_parse_mode() {
@ -131,21 +132,28 @@ fn (mut p Parser) detect_parse_mode() {
p.mode = .invalid p.mode = .invalid
return return
} }
p.tok = p.scanner.scan() p.tok = p.scanner.scan()
p.n_tok = p.scanner.scan() p.n_tok = p.scanner.scan()
if src.len == 1 && p.tok.kind == .string && p.n_tok.kind == .eof { if src.len == 1 && p.tok.kind == .string && p.n_tok.kind == .eof {
p.mode = .invalid p.mode = .invalid
return return
} }
match p.tok.kind { match p.tok.kind {
.lcbr { p.mode = .object } .lcbr {
.lsbr { p.mode = .array } p.mode = .object
.number { p.mode = .number } }
.key_true, .key_false { p.mode = .bool } .lsbr {
.string { p.mode = .string } p.mode = .array
}
.number {
p.mode = .number
}
.key_true, .key_false {
p.mode = .bool
}
.string {
p.mode = .string
}
.name { .name {
if p.tok.lit == 'null' { if p.tok.lit == 'null' {
p.mode = .null p.mode = .null
@ -164,11 +172,10 @@ fn (mut p Parser) decode_value() ?Any {
if p.n_level == 500 { if p.n_level == 500 {
return error('reached maximum nesting level of 500.') return error('reached maximum nesting level of 500.')
} }
if (p.tok.kind == .lsbr && p.n_tok.kind == .lcbr) ||
if (p.tok.kind == .lsbr && p.n_tok.kind == .lcbr) || (p.p_tok.kind == p.tok.kind && p.tok.kind == .lsbr) { (p.p_tok.kind == p.tok.kind && p.tok.kind == .lsbr) {
p.n_level++ p.n_level++
} }
match p.tok.kind { match p.tok.kind {
.lsbr { .lsbr {
return p.decode_array() return p.decode_array()
@ -181,59 +188,64 @@ fn (mut p Parser) decode_value() ?Any {
} }
.key_true { .key_true {
p.next() p.next()
return if p.convert_type { Any(true) } else { Any('true') } return if p.convert_type {
Any(true)
} else {
Any('true')
}
} }
.key_false { .key_false {
p.next() p.next()
return if p.convert_type { Any(false) } else { Any('false') } return if p.convert_type {
Any(false)
} else {
Any('false')
}
} }
.name { .name {
if p.tok.lit != 'null' { if p.tok.lit != 'null' {
return error('unknown identifier `$p.tok.lit`') return error('unknown identifier `$p.tok.lit`')
} }
p.next() p.next()
return if p.convert_type { Any(Null{}) } else { Any('null') } return if p.convert_type {
Any(Null{})
} else {
Any('null')
}
} }
.string { .string {
if p.is_singlequote() { if p.is_singlequote() {
return error('strings must be in double-quotes.') return error('strings must be in double-quotes.')
} }
return p.decode_string() return p.decode_string()
} }
else { else {
if p.tok.kind == .minus && p.n_tok.kind == .number && p.n_tok.pos == p.tok.pos+1 { if p.tok.kind == .minus && p.n_tok.kind == .number && p.n_tok.pos == p.tok.pos + 1 {
p.next() p.next()
d_num := p.decode_number()? d_num := p.decode_number() ?
return d_num return d_num
} }
return error("unknown token '$p.tok.lit' when decoding value")
return error('unknown token \'$p.tok.lit\' when decoding value')
} }
} }
if p.is_formfeed() { if p.is_formfeed() {
return error(formfeed_err) return error(formfeed_err)
} }
return Any{} return Any{}
} }
fn (mut p Parser) decode_string() ?Any { fn (mut p Parser) decode_string() ?Any {
mut strwr := strings.new_builder(200) mut strwr := strings.new_builder(200)
for i := 0; i < p.tok.lit.len; i++ { for i := 0; i < p.tok.lit.len; i++ {
if ((i-1 >= 0 && p.tok.lit[i-1] != `/`) || i == 0) && int(p.tok.lit[i]) in [9, 10, 0] { if ((i - 1 >= 0 && p.tok.lit[i - 1] != `/`) || i == 0) && int(p.tok.lit[i]) in [9, 10, 0] {
return error('character must be escaped with a backslash.') return error('character must be escaped with a backslash.')
} }
if i == p.tok.lit.len - 1 && p.tok.lit[i] == 92 {
if i == p.tok.lit.len-1 && p.tok.lit[i] == 92 {
return error('invalid backslash escape.') return error('invalid backslash escape.')
} }
if i + 1 < p.tok.lit.len && p.tok.lit[i] == 92 {
if i+1 < p.tok.lit.len && p.tok.lit[i] == 92 { peek := p.tok.lit[i + 1]
peek := p.tok.lit[i+1] match peek {
match peek{
`b` { `b` {
i++ i++
strwr.write_b(`\b`) strwr.write_b(`\b`)
@ -260,9 +272,9 @@ fn (mut p Parser) decode_string() ?Any {
continue continue
} }
`u` { `u` {
if i+5 < p.tok.lit.len { if i + 5 < p.tok.lit.len {
codepoint := p.tok.lit[i+2..i+6] codepoint := p.tok.lit[i + 2..i + 6]
check_valid_hex(codepoint)? check_valid_hex(codepoint) ?
hex_val := strconv.parse_int(codepoint, 16, 0) hex_val := strconv.parse_int(codepoint, 16, 0)
strwr.write_b(byte(hex_val)) strwr.write_b(byte(hex_val))
i += 5 i += 5
@ -288,16 +300,13 @@ fn (mut p Parser) decode_string() ?Any {
} }
else { return error('invalid backslash escape.') } else { return error('invalid backslash escape.') }
} }
if int(peek) == 85 { if int(peek) == 85 {
return error('unicode endpoints must be in lowercase `u`.') return error('unicode endpoints must be in lowercase `u`.')
} }
if int(peek) in [9, 229] { if int(peek) in [9, 229] {
return error('unicode endpoint not allowed.') return error('unicode endpoint not allowed.')
} }
} }
strwr.write_b(p.tok.lit[i]) strwr.write_b(p.tok.lit[i])
} }
p.next() p.next()
@ -314,23 +323,18 @@ fn (mut p Parser) decode_number() ?Any {
mut tl := p.tok.lit mut tl := p.tok.lit
mut is_fl := false mut is_fl := false
sep_by_dot := tl.to_lower().split('.') sep_by_dot := tl.to_lower().split('.')
if tl.starts_with('0x') && tl.all_after('0x').len <= 2 { if tl.starts_with('0x') && tl.all_after('0x').len <= 2 {
return error('hex numbers should not be less than or equal to two digits.') return error('hex numbers should not be less than or equal to two digits.')
} }
if src[p.p_tok.pos + p.p_tok.len] == `0` && src[p.p_tok.pos + p.p_tok.len + 1].is_digit() { if src[p.p_tok.pos + p.p_tok.len] == `0` && src[p.p_tok.pos + p.p_tok.len + 1].is_digit() {
return error('leading zeroes in integers are not allowed.') return error('leading zeroes in integers are not allowed.')
} }
if tl.starts_with('.') { if tl.starts_with('.') {
return error('decimals must start with a digit followed by a dot.') return error('decimals must start with a digit followed by a dot.')
} }
if tl.ends_with('+') || tl.ends_with('-') { if tl.ends_with('+') || tl.ends_with('-') {
return error('exponents must have a digit before the sign.') return error('exponents must have a digit before the sign.')
} }
if sep_by_dot.len > 1 { if sep_by_dot.len > 1 {
// analyze json number structure // analyze json number structure
// -[digit][dot][digit][E/e][-/+][digit] // -[digit][dot][digit][E/e][-/+][digit]
@ -341,11 +345,9 @@ fn (mut p Parser) decode_number() ?Any {
return error('exponents must have a digit before the exponent notation.') return error('exponents must have a digit before the exponent notation.')
} }
} }
if p.p_tok.kind == .minus && p.tok.pos == p.p_tok.pos + 1 {
if p.p_tok.kind == .minus && p.tok.pos == p.p_tok.pos+1 {
tl = '-$tl' tl = '-$tl'
} }
p.next() p.next()
if p.convert_type { if p.convert_type {
return if is_fl { return if is_fl {
@ -354,7 +356,6 @@ fn (mut p Parser) decode_number() ?Any {
Any(tl.i64()) Any(tl.i64())
} }
} }
return Any(tl) return Any(tl)
} }
@ -365,62 +366,51 @@ fn (mut p Parser) decode_array() ?Any {
if p.tok.kind == .eof { if p.tok.kind == .eof {
return error(eof_err) return error(eof_err)
} }
item := p.decode_value() ?
item := p.decode_value()?
items << item items << item
if p.tok.kind == .comma && p.n_tok.kind !in [.rsbr, .comma] { if p.tok.kind == .comma && p.n_tok.kind !in [.rsbr, .comma] {
p.next() p.next()
continue continue
} }
if p.tok.kind == .rsbr { if p.tok.kind == .rsbr {
break break
} }
return error("unknown token '$p.tok.lit' when decoding arrays.")
return error('unknown token \'$p.tok.lit\' when decoding arrays.')
} }
p.next() p.next()
return Any(items) return Any(items)
} }
fn (mut p Parser) decode_object() ?Any { fn (mut p Parser) decode_object() ?Any {
mut fields := map[string]Any mut fields := map[string]Any{}
mut cur_key := '' mut cur_key := ''
p.next() p.next()
for p.tok.kind != .rcbr { for p.tok.kind != .rcbr {
is_key := p.tok.kind == .string && p.n_tok.kind == .colon is_key := p.tok.kind == .string && p.n_tok.kind == .colon
// todo // todo
// if p.is_formfeed() { // if p.is_formfeed() {
// return error(formfeed_err) // return error(formfeed_err)
// } // }
if p.tok.kind == .eof { if p.tok.kind == .eof {
return error(eof_err) return error(eof_err)
} }
if p.is_singlequote() { if p.is_singlequote() {
return error('object keys must be in single quotes.') return error('object keys must be in single quotes.')
} }
if !is_key { if !is_key {
return error('invalid token `$p.tok.lit`, expected \'string\'') return error("invalid token `$p.tok.lit`, expected \'string\'")
} }
cur_key = p.tok.lit cur_key = p.tok.lit
p.next() p.next()
p.next() p.next()
fields[cur_key] = p.decode_value() ?
fields[cur_key] = p.decode_value()?
if p.tok.kind == .comma && p.n_tok.kind !in [.rcbr, .comma] { if p.tok.kind == .comma && p.n_tok.kind !in [.rcbr, .comma] {
p.next() p.next()
continue continue
} else if p.tok.kind == .rcbr { } else if p.tok.kind == .rcbr {
break break
} }
return error("unknown token '$p.tok.lit' when decoding object.")
return error('unknown token \'$p.tok.lit\' when decoding object.')
} }
p.next() p.next()
return Any(fields) return Any(fields)

View File

@ -0,0 +1,61 @@
import x.json2
fn test_raw_decode_string() {
str := json2.raw_decode('"Hello!"') or {
assert false
json2.Any{}
}
assert str.str() == 'Hello!'
}
fn test_raw_decode_number() {
num := json2.raw_decode('123') or {
assert false
json2.Any{}
}
assert num.int() == 123
}
fn test_raw_decode_array() {
raw_arr := json2.raw_decode('["Foo", 1]') or {
assert false
json2.Any{}
}
arr := raw_arr.arr()
assert arr[0].str() == 'Foo'
assert arr[1].int() == 1
}
fn test_raw_decode_bool() {
bol := json2.raw_decode('false') or {
assert false
json2.Any{}
}
assert bol.bool() == false
}
fn test_raw_decode_map() {
raw_mp := json2.raw_decode('{"name":"Bob","age":20}') or {
assert false
json2.Any{}
}
mp := raw_mp.as_map()
assert mp['name'].str() == 'Bob'
assert mp['age'].int() == 20
}
fn test_raw_decode_null() {
nul := json2.raw_decode('null') or {
assert false
json2.Any{}
}
assert nul is json2.Null
}
fn test_raw_decode_invalid() {
json2.raw_decode('1z') or {
assert err == '[json] invalid JSON. (0:0)'
return
}
assert false
}

View File

@ -12,11 +12,13 @@ fn write_value(v Any, i int, len int, mut wr strings.Builder) {
} else { } else {
wr.write(str) wr.write(str)
} }
if i >= len-1 { return } if i >= len - 1 {
return
}
wr.write_b(`,`) wr.write_b(`,`)
} }
// String representation of the `map[string]Any`. // str returns the string representation of the `map[string]Any`.
pub fn (flds map[string]Any) str() string { pub fn (flds map[string]Any) str() string {
mut wr := strings.new_builder(200) mut wr := strings.new_builder(200)
wr.write_b(`{`) wr.write_b(`{`)
@ -34,7 +36,7 @@ pub fn (flds map[string]Any) str() string {
return res return res
} }
// String representation of the `[]Any`. // str returns the string representation of the `[]Any`.
pub fn (flds []Any) str() string { pub fn (flds []Any) str() string {
mut wr := strings.new_builder(200) mut wr := strings.new_builder(200)
wr.write_b(`[`) wr.write_b(`[`)
@ -49,19 +51,49 @@ pub fn (flds []Any) str() string {
return res return res
} }
// String representation of the `Any` type. // str returns the string representation of the `Any` type.
pub fn (f Any) str() string { pub fn (f Any) str() string {
match f { match f {
string { return f } string {
int { return f.str() } return f
i64 { return f.str() } }
f32 { return f.str() } int {
f64 { return f.str() } return f.str()
any_int { return f.str() } }
any_float { return f.str() } i64 {
bool { return f.str() } return f.str()
map[string]Any { return f.str() } }
Null { return 'null' } f32 {
str_f32 := f.str()
return if str_f32.ends_with('.') {
str_f32 + '0'
} else {
str_f32
}
}
f64 {
str_f64 := f.str()
return if str_f64.ends_with('.') {
str_f64 + '0'
} else {
str_f64
}
}
any_int {
return f.str()
}
any_float {
return f.str()
}
bool {
return f.str()
}
map[string]Any {
return f.str()
}
Null {
return 'null'
}
else { else {
if f is []Any { if f is []Any {
return f.str() return f.str()

View File

@ -3,6 +3,10 @@
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
module json2 module json2
pub const (
null = Null{}
)
pub interface Serializable { pub interface Serializable {
from_json(f Any) from_json(f Any)
to_json() string to_json() string
@ -19,110 +23,73 @@ pub fn fast_raw_decode(src string) ?Any {
mut p := new_parser(src, false) mut p := new_parser(src, false)
return p.decode() return p.decode()
} }
// A generic function that decodes a JSON string into the target type.
// // decode is a generic function that decodes a JSON string into the target type.
// TODO: decode must return an optional generics pub fn decode<T>(src string) ?T {
pub fn decode<T>(src string) T { res := raw_decode(src) ?
res := raw_decode(src) or {
panic(err)
}
mut typ := T{} mut typ := T{}
typ.from_json(res) typ.from_json(res)
return typ return typ
} }
// TODO: decode must return an optional generics
// pub fn decode2<T>(src string) ?T {
// res := raw_decode(src)?
// match typeof(T) { // encode is a generic function that encodes a type into a JSON string.
// 'string' {
// return res.str()
// }
// 'int' {
// return res.int()
// }
// 'f64' {
// return res.f64()
// }
// else {
// mut typ := T{}
// typ.from_json(res)
// return typ
// }
// }
// }
// A generic function that encodes a type into a JSON string.
pub fn encode<T>(typ T) string { pub fn encode<T>(typ T) string {
// if typeof(typ) in ['string', 'int', 'f64'] {
// return Any(typ).str()
// }
return typ.to_json() return typ.to_json()
} }
// A simple function that returns `Null` struct. For use on constructing an `Any` object.
pub fn null() Null { // as_map uses `Any` as a map.
return Null{}
}
// Use `Any` as a map.
pub fn (f Any) as_map() map[string]Any { pub fn (f Any) as_map() map[string]Any {
if f is map[string]Any { if f is map[string]Any {
return f return f
} else if f is []Any { } else if f is []Any {
mut mp := map[string]Any mut mp := map[string]Any{}
for i, fi in f { for i, fi in f {
mp['$i'] = fi mp['$i'] = fi
} }
return mp return mp
} }
return { '0': f } return {
'0': f
}
} }
// Use `Any` as an integer. // int uses `Any` as an integer.
pub fn (f Any) int() int { pub fn (f Any) int() int {
match f { match f {
int { return f } int { return f }
i64 { return int(f) } i64, f32, f64, bool { return int(f) }
f64 { return f.str().int() }
f32 { return f.str().int() }
bool { return int(f) }
else { return 0 } else { return 0 }
} }
} }
// Use `Any` as a 64-bit integer. // i64 uses `Any` as a 64-bit integer.
pub fn (f Any) i64() i64 { pub fn (f Any) i64() i64 {
match f { match f {
int { return f } i64 { return f }
i64 { return int(f) } int, f32, f64, bool { return i64(f) }
f64 { return f.str().i64() }
f32 { return f.str().i64() }
bool { return int(f) }
else { return 0 } else { return 0 }
} }
} }
// Use `Any` as a 32-bit float. // f32 uses `Any` as a 32-bit float.
pub fn (f Any) f32() f32 { pub fn (f Any) f32() f32 {
match f { match f {
int { return f }
i64 { return f.str().f32() }
f64 { return f.str().f32() }
f32 { return f } f32 { return f }
int, i64, f64 { return f32(f) }
else { return 0.0 } else { return 0.0 }
} }
} }
// Use `Any` as a float. // f64 uses `Any` as a float.
pub fn (f Any) f64() f64 { pub fn (f Any) f64() f64 {
match f { match f {
int { return f }
i64 { return f }
f64 { return f } f64 { return f }
f32 { return f.str().f64() } int, i64, f32 { return f64(f) }
else { return 0.0 } else { return 0.0 }
} }
} }
// Use `Any` as an array.
// arr uses `Any` as an array.
pub fn (f Any) arr() []Any { pub fn (f Any) arr() []Any {
if f is []Any { if f is []Any {
return f return f
@ -136,7 +103,7 @@ pub fn (f Any) arr() []Any {
return [f] return [f]
} }
// Use `Any` as a bool // bool uses `Any` as a bool
pub fn (f Any) bool() bool { pub fn (f Any) bool() bool {
match f { match f {
bool { return f } bool { return f }

View File

@ -7,6 +7,7 @@ enum JobTitle {
} }
struct Employee { struct Employee {
pub mut:
name string name string
age int age int
salary f32 salary f32
@ -14,12 +15,11 @@ struct Employee {
} }
fn (e Employee) to_json() string { fn (e Employee) to_json() string {
mut mp := map[string]json2.Any mut mp := map[string]json2.Any{}
mp['name'] = e.name mp['name'] = e.name
mp['age'] = e.age mp['age'] = e.age
mp['salary'] = e.salary mp['salary'] = e.salary
mp['title'] = int(e.title) mp['title'] = int(e.title)
/* /*
$for field in Employee.fields { $for field in Employee.fields {
d := e.$(field.name) d := e.$(field.name)
@ -31,31 +31,31 @@ fn (e Employee) to_json() string {
} }
} }
*/ */
return mp.str() return mp.str()
} }
fn (mut e Employee) from_json(any json2.Any) {
mp := any.as_map()
e.name = mp['name'].str()
e.age = mp['age'].int()
e.salary = mp['salary'].f32()
e.title = JobTitle(mp['title'].int())
}
fn test_simple() { fn test_simple() {
x := Employee{'Peter', 28, 95000.5, .worker} x := Employee{'Peter', 28, 95000.5, .worker}
s := json2.encode<Employee>(x) s := json2.encode<Employee>(x)
eprintln('Employee x: $s') eprintln('Employee x: $s')
assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2}' assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2}'
/* y := json2.decode<Employee>(s) or {
y := json.decode(Employee, s) or {
assert false assert false
Employee{} Employee{}
}*/
y := json2.raw_decode(s) or {
assert false
json2.Any{}
} }
eprintln('Employee y: $y') eprintln('Employee y: $y')
ym := y.as_map() assert y.name == 'Peter'
assert ym['name'].str() == 'Peter' assert y.age == 28
assert ym['age'].int() == 28 assert y.salary == 95000.5
assert ym['salary'].f64() == 95000.5 assert y.title == .worker
// assert y['title'] == .worker
assert ym['title'].int() == 2
} }
fn test_fast_raw_decode() { fn test_fast_raw_decode() {
@ -65,7 +65,6 @@ fn test_fast_raw_decode() {
json2.Any{} json2.Any{}
} }
str := o.str() str := o.str()
assert str == '{"name":"Peter","age":"28","salary":"95000.5","title":"2"}' assert str == '{"name":"Peter","age":"28","salary":"95000.5","title":"2"}'
} }
@ -83,7 +82,7 @@ fn test_character_unescape() {
json2.Any{} json2.Any{}
} }
lines := obj.as_map() lines := obj.as_map()
eprintln("$lines") eprintln('$lines')
assert lines['newline'].str() == 'new\nline' assert lines['newline'].str() == 'new\nline'
assert lines['tab'].str() == '\ttab' assert lines['tab'].str() == '\ttab'
assert lines['backslash'].str() == 'back\\slash' assert lines['backslash'].str() == 'back\\slash'
@ -91,13 +90,34 @@ fn test_character_unescape() {
assert lines['slash'].str() == '/dev/null' assert lines['slash'].str() == '/dev/null'
} }
/*
struct User2 { struct User2 {
pub mut:
age int age int
nums []int nums []int
} }
fn (mut u User2) from_json(an json2.Any) {
mp := an.as_map()
mut js_field_name := ''
$for field in User.fields {
js_field_name = field.name
for attr in field.attrs {
if attr.starts_with('json:') {
js_field_name = attr.all_after('json:').trim_left(' ')
break
}
}
match field.name {
'age' { u.age = mp[js_field_name].int() }
'nums' { u.nums = mp[js_field_name].arr().map(it.int()) }
else {}
}
}
}
// User struct needs to be `pub mut` for now in order to access and manipulate values
struct User { struct User {
pub mut:
age int age int
nums []int nums []int
last_name string [json: lastName] last_name string [json: lastName]
@ -106,16 +126,53 @@ struct User {
pets string [raw; json: 'pet_animals'] pets string [raw; json: 'pet_animals']
} }
fn (mut u User) from_json(an json2.Any) {
mp := an.as_map()
mut js_field_name := ''
$for field in User.fields {
// FIXME: C error when initializing js_field_name inside comptime for
js_field_name = field.name
for attr in field.attrs {
if attr.starts_with('json:') {
js_field_name = attr.all_after('json:').trim_left(' ')
break
}
}
match field.name {
'age' { u.age = mp[js_field_name].int() }
'nums' { u.nums = mp[js_field_name].arr().map(it.int()) }
'last_name' { u.last_name = mp[js_field_name].str() }
'is_registered' { u.is_registered = mp[js_field_name].bool() }
'typ' { u.typ = mp[js_field_name].int() }
'pets' { u.pets = mp[js_field_name].str() }
else {}
}
}
}
fn (u User) to_json() string {
// TODO: derive from field
mut mp := {
'age': json2.Any(u.age)
}
mp['nums'] = u.nums.map(json2.Any(it))
mp['lastName'] = u.last_name
mp['IsRegistered'] = u.is_registered
mp['type'] = u.typ
mp['pet_animals'] = u.pets
return mp.str()
}
fn test_parse_user() { fn test_parse_user() {
s := '{"age": 10, "nums": [1,2,3], "type": 1, "lastName": "Johnson", "IsRegistered": true, "pet_animals": {"name": "Bob", "animal": "Dog"}}' s := '{"age": 10, "nums": [1,2,3], "type": 1, "lastName": "Johnson", "IsRegistered": true, "pet_animals": {"name": "Bob", "animal": "Dog"}}'
u2 := json.decode(User2, s) or { u2 := json2.decode<User2>(s) or {
exit(1) assert false
User2{}
} }
println(u2) u := json2.decode<User>(s) or {
u := json.decode(User, s) or { assert false
exit(1) User{}
} }
println(u)
assert u.age == 10 assert u.age == 10
assert u.last_name == 'Johnson' assert u.last_name == 'Johnson'
assert u.is_registered == true assert u.is_registered == true
@ -137,25 +194,37 @@ fn test_encode_user() {
pets: 'foo' pets: 'foo'
} }
expected := '{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"foo"}' expected := '{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"foo"}'
out := json.encode(usr) out := json2.encode<User>(usr)
println(out)
assert out == expected assert out == expected
} }
struct Color { struct Color {
pub mut:
space string space string
point string [raw] point string [raw]
} }
fn (mut c Color) from_json(an json2.Any) {
mp := an.as_map()
$for field in Color.fields {
match field.name {
'space' { c.space = mp[field.name].str() }
'point' { c.point = mp[field.name].str() }
else {}
}
}
}
fn test_raw_json_field() { fn test_raw_json_field() {
color := json.decode(Color, '{"space": "YCbCr", "point": {"Y": 123}}') or { color := json2.decode<Color>('{"space": "YCbCr", "point": {"Y": 123}}') or {
println('text') assert false
return Color{}
} }
assert color.point == '{"Y":123}' assert color.point == '{"Y":123}'
assert color.space == 'YCbCr' assert color.space == 'YCbCr'
} }
/*
struct City { struct City {
name string name string
} }
@ -177,7 +246,6 @@ fn test_struct_in_struct() {
println(country.cities) println(country.cities)
} }
*/ */
fn test_encode_map() { fn test_encode_map() {
expected := '{"one":1,"two":2,"three":3,"four":4}' expected := '{"one":1,"two":2,"three":3,"four":4}'
numbers := { numbers := {
@ -187,9 +255,6 @@ fn test_encode_map() {
'four': json2.Any(4) 'four': json2.Any(4)
} }
out := numbers.str() out := numbers.str()
// out := json.encode(numbers)
println(out)
assert out == expected assert out == expected
} }
/* /*