1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

x.json2: generic-based encoder (finish PR#15137) (#16464)

This commit is contained in:
Hitalo Souza
2022-11-18 06:09:24 -03:00
committed by GitHub
parent 44e2149baa
commit 74613bd636
6 changed files with 210 additions and 54 deletions

View File

@@ -2,5 +2,5 @@ import x.json2
fn main() { fn main() {
x := '[[],[],[]]' x := '[[],[],[]]'
println(json2.raw_decode(x)?) println(json2.raw_decode(x)!)
} }

View File

@@ -4,7 +4,17 @@
module json2 module json2
// `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 = Null | []Any | bool | f32 | f64 | i64 | int | map[string]Any | string | u64 pub type Any = Null
| []Any
| []int
| bool
| f32
| f64
| i64
| int
| map[string]Any
| string
| u64
// `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 {

View File

@@ -14,6 +14,8 @@ pub struct Encoder {
escape_unicode bool = true escape_unicode bool = true
} }
pub const default_encoder = Encoder{}
// byte array versions of the most common tokens/chars // byte array versions of the most common tokens/chars
// to avoid reallocations // to avoid reallocations
const null_in_bytes = 'null'.bytes() const null_in_bytes = 'null'.bytes()
@@ -37,9 +39,9 @@ const quote_bytes = [u8(`"`)]
const escaped_chars = [(r'\b').bytes(), (r'\f').bytes(), (r'\n').bytes(), const escaped_chars = [(r'\b').bytes(), (r'\f').bytes(), (r'\n').bytes(),
(r'\r').bytes(), (r'\t').bytes()] (r'\r').bytes(), (r'\t').bytes()]
// encode_value encodes an `Any` value to the specific writer. // encode_value encodes a value to the specific writer.
pub fn (e &Encoder) encode_value(f Any, mut wr io.Writer) ! { pub fn (e &Encoder) encode_value<T>(val T, mut wr io.Writer) ! {
e.encode_value_with_level(f, 1, mut wr)! e.encode_value_with_level<T>(val, 1, mut wr)!
} }
fn (e &Encoder) encode_newline(level int, mut wr io.Writer) ! { fn (e &Encoder) encode_newline(level int, mut wr io.Writer) ! {
@@ -51,24 +53,24 @@ fn (e &Encoder) encode_newline(level int, mut wr io.Writer) ! {
} }
} }
fn (e &Encoder) encode_value_with_level(f Any, level int, mut wr io.Writer) ! { fn (e &Encoder) encode_any(val Any, level int, mut wr io.Writer) ! {
match f { match val {
string { string {
e.encode_string(f, mut wr)! e.encode_string(val, mut wr)!
} }
bool { bool {
if f == true { if val == true {
wr.write(json2.true_in_bytes)! wr.write(json2.true_in_bytes)!
} else { } else {
wr.write(json2.false_in_bytes)! wr.write(json2.false_in_bytes)!
} }
} }
int, u64, i64 { int, u64, i64 {
wr.write(f.str().bytes())! wr.write(val.str().bytes())!
} }
f32, f64 { f32, f64 {
$if !nofloat ? { $if !nofloat ? {
str_float := f.str().bytes() str_float := val.str().bytes()
wr.write(str_float)! wr.write(str_float)!
if str_float[str_float.len - 1] == `.` { if str_float[str_float.len - 1] == `.` {
wr.write(json2.zero_in_bytes)! wr.write(json2.zero_in_bytes)!
@@ -80,7 +82,7 @@ fn (e &Encoder) encode_value_with_level(f Any, level int, mut wr io.Writer) ! {
map[string]Any { map[string]Any {
wr.write([u8(`{`)])! wr.write([u8(`{`)])!
mut i := 0 mut i := 0
for k, v in f { for k, v in val {
e.encode_newline(level, mut wr)! e.encode_newline(level, mut wr)!
e.encode_string(k, mut wr)! e.encode_string(k, mut wr)!
wr.write(json2.colon_bytes)! wr.write(json2.colon_bytes)!
@@ -88,7 +90,7 @@ fn (e &Encoder) encode_value_with_level(f Any, level int, mut wr io.Writer) ! {
wr.write(json2.space_bytes)! wr.write(json2.space_bytes)!
} }
e.encode_value_with_level(v, level + 1, mut wr)! e.encode_value_with_level(v, level + 1, mut wr)!
if i < f.len - 1 { if i < val.len - 1 {
wr.write(json2.comma_bytes)! wr.write(json2.comma_bytes)!
} }
i++ i++
@@ -98,13 +100,27 @@ fn (e &Encoder) encode_value_with_level(f Any, level int, mut wr io.Writer) ! {
} }
[]Any { []Any {
wr.write([u8(`[`)])! wr.write([u8(`[`)])!
for i, v in f { for i in 0 .. val.len {
e.encode_newline(level, mut wr)! e.encode_newline(level, mut wr)!
e.encode_value_with_level(v, level + 1, mut wr)! e.encode_value_with_level(val[i], level + 1, mut wr)!
if i < f.len - 1 { if i < val.len - 1 {
wr.write(json2.comma_bytes)! wr.write(json2.comma_bytes)!
} }
} }
e.encode_newline(level - 1, mut wr)!
wr.write([u8(`]`)])!
}
[]int {
wr.write([u8(`[`)])!
for i in 0 .. val.len {
e.encode_newline(level, mut wr)!
e.encode_value_with_level(val[i], level + 1, mut wr)!
if i < val.len - 1 {
wr.write(json2.comma_bytes)!
}
}
e.encode_newline(level - 1, mut wr)! e.encode_newline(level - 1, mut wr)!
wr.write([u8(`]`)])! wr.write([u8(`]`)])!
} }
@@ -114,6 +130,85 @@ fn (e &Encoder) encode_value_with_level(f Any, level int, mut wr io.Writer) ! {
} }
} }
fn (e &Encoder) encode_value_with_level<T>(val T, level int, mut wr io.Writer) ! {
$if T is string {
e.encode_string(val, mut wr)!
} $else $if T is Any {
e.encode_any(val, level, mut wr)!
} $else $if T is map[string]Any {
// weird quirk but val is destructured immediately to Any
e.encode_any(val, level, mut wr)!
} $else $if T is []Any {
e.encode_any(val, level, mut wr)!
} $else $if T is Null || T is bool || T is f32 || T is f64 || T is i64 || T is int || T is u64 {
e.encode_any(val, level, mut wr)!
} $else $if T is Encodable {
wr.write(val.json_str().bytes())!
} $else $if T is []int {
// wr.write(val.str)!
e.encode_any(val, level, mut wr)!
} $else $if T is $Struct {
e.encode_struct(val, level, mut wr)!
} $else $if T is $Enum {
e.encode_any(Any(int(val)), level, mut wr)!
} $else {
return error('cannot encode value with ${typeof(val).name} type')
}
}
fn (e &Encoder) encode_struct<U>(val U, level int, mut wr io.Writer) ! {
wr.write([u8(`{`)])!
mut i := 0
mut fields_len := 0
$for _ in U.fields {
fields_len++
}
$for field in U.fields {
mut json_name := ''
for attr in field.attrs {
if attr.contains('json: ') {
json_name = attr.replace('json: ', '')
break
}
}
e.encode_newline(level, mut wr)!
if json_name != '' {
e.encode_string(json_name, mut wr)!
} else {
e.encode_string(field.name, mut wr)!
}
wr.write(json2.colon_bytes)!
if e.newline != 0 {
wr.write(json2.space_bytes)!
}
field_value := val.$(field.name)
e.encode_value_with_level(field_value, level + 1, mut wr)!
if i < fields_len - 1 {
wr.write(json2.comma_bytes)!
}
i++
}
e.encode_newline(level - 1, mut wr)!
wr.write([u8(`}`)])!
}
fn (e &Encoder) encode_array<U>(val U, level int, mut wr io.Writer) ! {
$if U is $Array {
wr.write([u8(`[`)])!
for i in 0 .. val.len {
e.encode_newline(level, mut wr)!
e.encode_value_with_level(&val[i], level + 1, mut wr)!
if i < val.len - 1 {
wr.write(json2.comma_bytes)!
}
}
e.encode_newline(level - 1, mut wr)!
wr.write([u8(`]`)])!
} $else {
return error('encoded array value is not an array')
}
}
// str returns the JSON string representation of the `map[string]Any` type. // str returns the JSON string representation of the `map[string]Any` type.
pub fn (f map[string]Any) str() string { pub fn (f map[string]Any) str() string {
return Any(f).json_str() return Any(f).json_str()
@@ -137,13 +232,7 @@ pub fn (f Any) str() string {
// json_str returns the JSON string representation of the `Any` type. // json_str returns the JSON string representation of the `Any` type.
[manualfree] [manualfree]
pub fn (f Any) json_str() string { pub fn (f Any) json_str() string {
mut sb := strings.new_builder(4096) return encode(f)
defer {
unsafe { sb.free() }
}
mut enc := Encoder{}
enc.encode_value(f, mut sb) or { return '' }
return sb.str()
} }
// prettify_json_str returns the pretty-formatted JSON string representation of the `Any` type. // prettify_json_str returns the pretty-formatted JSON string representation of the `Any` type.
@@ -157,7 +246,7 @@ pub fn (f Any) prettify_json_str() string {
newline: `\n` newline: `\n`
newline_spaces_count: 4 newline_spaces_count: 4
} }
enc.encode_value(f, mut sb) or { return '' } enc.encode_value(f, mut sb) or {}
return sb.str() return sb.str()
} }

View File

@@ -38,19 +38,20 @@ fn test_json_string_non_ascii() {
fn test_utf8_strings_are_not_modified() { fn test_utf8_strings_are_not_modified() {
original := '{"s":"Schilddrüsenerkrankungen"}' original := '{"s":"Schilddrüsenerkrankungen"}'
// dump(original)
deresult := json2.raw_decode(original)! deresult := json2.raw_decode(original)!
// dump(deresult)
assert deresult.str() == original assert deresult.str() == original
} }
fn test_encoder_unescaped_utf32() { fn test_encoder_unescaped_utf32() ! {
jap_text := json2.Any('') jap_text := json2.Any('')
enc := json2.Encoder{ enc := json2.Encoder{
escape_unicode: false escape_unicode: false
} }
mut sb := strings.new_builder(20) mut sb := strings.new_builder(20)
defer {
unsafe { sb.free() }
}
enc.encode_value(jap_text, mut sb)! enc.encode_value(jap_text, mut sb)!
assert sb.str() == '"${jap_text}"' assert sb.str() == '"${jap_text}"'
@@ -74,6 +75,9 @@ fn test_encoder_prettify() {
newline_spaces_count: 2 newline_spaces_count: 2
} }
mut sb := strings.new_builder(20) mut sb := strings.new_builder(20)
defer {
unsafe { sb.free() }
}
enc.encode_value(obj, mut sb)! enc.encode_value(obj, mut sb)!
assert sb.str() == '{ assert sb.str() == '{
"hello": "world", "hello": "world",
@@ -88,3 +92,34 @@ fn test_encoder_prettify() {
} }
}' }'
} }
pub struct Test {
val string
}
fn test_encode_struct() {
enc := json2.encode(Test{'hello!'})
assert enc == '{"val":"hello!"}'
}
pub struct Uri {
protocol string
path string
}
pub fn (u Uri) json_str() string {
return '"${u.protocol}://${u.path}"'
}
fn test_encode_encodable() {
assert json2.encode(Uri{'file', 'path/to/file'}) == '"file://path/to/file"'
}
fn test_encode_array() {
assert json2.encode([1, 2, 3]) == '[1,2,3]'
}
fn test_encode_simple() {
assert json2.encode('hello!') == '"hello!"'
assert json2.encode(1) == '1'
}

View File

@@ -3,13 +3,18 @@
// that can be found in the LICENSE file. // that can be found in the LICENSE file.
module json2 module json2
import strings
pub const ( pub const (
null = Null{} null = Null{}
) )
pub interface Serializable { pub interface Decodable {
from_json(f Any) from_json(f Any)
to_json() string }
pub interface Encodable {
json_str() string
} }
// Decodes a JSON string into an `Any` type. Returns an option. // Decodes a JSON string into an `Any` type. Returns an option.
@@ -33,8 +38,16 @@ pub fn decode<T>(src string) !T {
} }
// encode is a generic function that encodes a type into a JSON string. // encode is a generic function that encodes a type into a JSON string.
pub fn encode<T>(typ T) string { pub fn encode<T>(val T) string {
return typ.to_json() mut sb := strings.new_builder(64)
defer {
unsafe { sb.free() }
}
default_encoder.encode_value(val, mut sb) or {
dump(err)
default_encoder.encode_value<Null>(json2.null, mut sb) or {}
}
return sb.str()
} }
// as_map uses `Any` as a map. // as_map uses `Any` as a map.

View File

@@ -1,4 +1,5 @@
import x.json2 import x.json2
import time
enum JobTitle { enum JobTitle {
manager manager
@@ -45,14 +46,12 @@ fn (mut e Employee) from_json(any json2.Any) {
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}')
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 := json2.decode<Employee>(s) or {
println(err) println(err)
assert false assert false
return return
} }
eprintln('Employee y: ${y}')
assert y.name == 'Peter' assert y.name == 'Peter'
assert y.age == 28 assert y.age == 28
assert y.salary == 95000.5 assert y.salary == 95000.5
@@ -83,7 +82,6 @@ fn test_character_unescape() {
return return
} }
lines := obj.as_map() lines := obj.as_map()
eprintln('${lines}')
assert lines['newline'] or { 0 }.str() == 'new\nline' assert lines['newline'] or { 0 }.str() == 'new\nline'
assert lines['tab'] or { 0 }.str() == '\ttab' assert lines['tab'] or { 0 }.str() == '\ttab'
assert lines['backslash'] or { 0 }.str() == 'back\\slash' assert lines['backslash'] or { 0 }.str() == 'back\\slash'
@@ -91,12 +89,6 @@ fn test_character_unescape() {
assert lines['slash'] or { 0 }.str() == '/dev/null' assert lines['slash'] or { 0 }.str() == '/dev/null'
} }
struct User2 {
pub mut:
age int
nums []int
}
fn (mut u User2) from_json(an json2.Any) { fn (mut u User2) from_json(an json2.Any) {
mp := an.as_map() mp := an.as_map()
mut js_field_name := '' mut js_field_name := ''
@@ -116,6 +108,13 @@ fn (mut u User2) from_json(an json2.Any) {
} }
} }
struct User2 {
mut:
age int
nums []int
reg_date time.Time
}
// User struct needs to be `pub mut` for now in order to access and manipulate values // User struct needs to be `pub mut` for now in order to access and manipulate values
struct User { struct User {
pub mut: pub mut:
@@ -166,17 +165,8 @@ fn (u User) to_json() string {
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 := json2.decode<User2>(s) or { u2 := json2.decode<User2>(s)!
println(err) u := json2.decode<User>(s)!
assert false
return
}
println(u2)
u := json2.decode<User>(s) or {
println(err)
assert false
return
}
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
@@ -188,8 +178,26 @@ fn test_parse_user() {
assert u.pets == '{"name":"Bob","animal":"Dog"}' assert u.pets == '{"name":"Bob","animal":"Dog"}'
} }
// fn test_encode_decode_time() {
// user := User2{
// age: 25
// reg_date: time.new_time(year: 2020, month: 12, day: 22, hour: 7, minute: 23)
// }
// s := json2.encode(user)
// // println(s) //{"age":25,"nums":[],"reg_date":{"year":2020,"month":12,"day":22,"hour":7,"minute":23,"second":0,"microsecond":0,"unix":1608621780,"is_local":false}}
// assert s.contains('"reg_date":1608621780')
// user2 := json2.decode<User2>(s)!
// assert user2.reg_date.str() == '2020-12-22 07:23:00'
// // println(user2)
// // println(user2.reg_date)
// }
fn (mut u User) foo() string {
return json2.encode(u)
}
fn test_encode_user() { fn test_encode_user() {
usr := User{ mut usr := User{
age: 10 age: 10
nums: [1, 2, 3] nums: [1, 2, 3]
last_name: 'Johnson' last_name: 'Johnson'
@@ -200,6 +208,8 @@ fn test_encode_user() {
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 := json2.encode<User>(usr) out := json2.encode<User>(usr)
assert out == expected assert out == expected
// Test json.encode on mutable pointers
assert usr.foo() == expected
} }
struct Color { struct Color {
@@ -237,9 +247,8 @@ struct Country {
cities []City cities []City
name string name string
} }
fn test_struct_in_struct() { fn test_struct_in_struct() {
country := json.decode(Country, '{ "name": "UK", "cities": [{"name":"London"}, {"name":"Manchester"}]}') or { country := json2.decode<Country>('{ "name": "UK", "cities": [{"name":"London"}, {"name":"Manchester"}]}') or {
assert false assert false
exit(1) exit(1)
} }