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:
parent
44e2149baa
commit
74613bd636
@ -2,5 +2,5 @@ import x.json2
|
||||
|
||||
fn main() {
|
||||
x := '[[],[],[]]'
|
||||
println(json2.raw_decode(x)?)
|
||||
println(json2.raw_decode(x)!)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user