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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 210 additions and 54 deletions

View File

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

View File

@ -4,7 +4,17 @@
module json2
// `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.
pub struct Null {

View File

@ -14,6 +14,8 @@ pub struct Encoder {
escape_unicode bool = true
}
pub const default_encoder = Encoder{}
// byte array versions of the most common tokens/chars
// to avoid reallocations
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(),
(r'\r').bytes(), (r'\t').bytes()]
// encode_value encodes an `Any` value to the specific writer.
pub fn (e &Encoder) encode_value(f Any, mut wr io.Writer) ! {
e.encode_value_with_level(f, 1, mut wr)!
// encode_value encodes a value to the specific writer.
pub fn (e &Encoder) encode_value<T>(val T, mut wr io.Writer) ! {
e.encode_value_with_level<T>(val, 1, mut wr)!
}
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) ! {
match f {
fn (e &Encoder) encode_any(val Any, level int, mut wr io.Writer) ! {
match val {
string {
e.encode_string(f, mut wr)!
e.encode_string(val, mut wr)!
}
bool {
if f == true {
if val == true {
wr.write(json2.true_in_bytes)!
} else {
wr.write(json2.false_in_bytes)!
}
}
int, u64, i64 {
wr.write(f.str().bytes())!
wr.write(val.str().bytes())!
}
f32, f64 {
$if !nofloat ? {
str_float := f.str().bytes()
str_float := val.str().bytes()
wr.write(str_float)!
if str_float[str_float.len - 1] == `.` {
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 {
wr.write([u8(`{`)])!
mut i := 0
for k, v in f {
for k, v in val {
e.encode_newline(level, mut wr)!
e.encode_string(k, mut wr)!
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)!
}
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)!
}
i++
@ -98,13 +100,27 @@ fn (e &Encoder) encode_value_with_level(f Any, level int, mut wr io.Writer) ! {
}
[]Any {
wr.write([u8(`[`)])!
for i, v in f {
for i in 0 .. val.len {
e.encode_newline(level, mut wr)!
e.encode_value_with_level(v, level + 1, mut wr)!
if i < f.len - 1 {
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(`]`)])!
}
[]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)!
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.
pub fn (f map[string]Any) str() string {
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.
[manualfree]
pub fn (f Any) json_str() string {
mut sb := strings.new_builder(4096)
defer {
unsafe { sb.free() }
}
mut enc := Encoder{}
enc.encode_value(f, mut sb) or { return '' }
return sb.str()
return encode(f)
}
// 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_spaces_count: 4
}
enc.encode_value(f, mut sb) or { return '' }
enc.encode_value(f, mut sb) or {}
return sb.str()
}

View File

@ -38,19 +38,20 @@ fn test_json_string_non_ascii() {
fn test_utf8_strings_are_not_modified() {
original := '{"s":"Schilddrüsenerkrankungen"}'
// dump(original)
deresult := json2.raw_decode(original)!
// dump(deresult)
assert deresult.str() == original
}
fn test_encoder_unescaped_utf32() {
fn test_encoder_unescaped_utf32() ! {
jap_text := json2.Any('')
enc := json2.Encoder{
escape_unicode: false
}
mut sb := strings.new_builder(20)
defer {
unsafe { sb.free() }
}
enc.encode_value(jap_text, mut sb)!
assert sb.str() == '"${jap_text}"'
@ -74,6 +75,9 @@ fn test_encoder_prettify() {
newline_spaces_count: 2
}
mut sb := strings.new_builder(20)
defer {
unsafe { sb.free() }
}
enc.encode_value(obj, mut sb)!
assert sb.str() == '{
"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.
module json2
import strings
pub const (
null = Null{}
)
pub interface Serializable {
pub interface Decodable {
from_json(f Any)
to_json() string
}
pub interface Encodable {
json_str() string
}
// 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.
pub fn encode<T>(typ T) string {
return typ.to_json()
pub fn encode<T>(val T) string {
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.

View File

@ -1,4 +1,5 @@
import x.json2
import time
enum JobTitle {
manager
@ -45,14 +46,12 @@ fn (mut e Employee) from_json(any json2.Any) {
fn test_simple() {
x := Employee{'Peter', 28, 95000.5, .worker}
s := json2.encode<Employee>(x)
eprintln('Employee x: ${s}')
assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2}'
y := json2.decode<Employee>(s) or {
println(err)
assert false
return
}
eprintln('Employee y: ${y}')
assert y.name == 'Peter'
assert y.age == 28
assert y.salary == 95000.5
@ -83,7 +82,6 @@ fn test_character_unescape() {
return
}
lines := obj.as_map()
eprintln('${lines}')
assert lines['newline'] or { 0 }.str() == 'new\nline'
assert lines['tab'] or { 0 }.str() == '\ttab'
assert lines['backslash'] or { 0 }.str() == 'back\\slash'
@ -91,12 +89,6 @@ fn test_character_unescape() {
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) {
mp := an.as_map()
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
struct User {
pub mut:
@ -166,17 +165,8 @@ fn (u User) to_json() string {
fn test_parse_user() {
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 {
println(err)
assert false
return
}
println(u2)
u := json2.decode<User>(s) or {
println(err)
assert false
return
}
u2 := json2.decode<User2>(s)!
u := json2.decode<User>(s)!
assert u.age == 10
assert u.last_name == 'Johnson'
assert u.is_registered == true
@ -188,8 +178,26 @@ fn test_parse_user() {
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() {
usr := User{
mut usr := User{
age: 10
nums: [1, 2, 3]
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"}'
out := json2.encode<User>(usr)
assert out == expected
// Test json.encode on mutable pointers
assert usr.foo() == expected
}
struct Color {
@ -237,9 +247,8 @@ struct Country {
cities []City
name string
}
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
exit(1)
}