x.json2: add skippable field attr @[json: '-'] (improve backwards compatibility with the json module) (#20892)

This commit is contained in:
johnpgr 2024-02-26 16:05:38 -03:00 committed by GitHub
parent 71bd94aa9e
commit c6048d50bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 322 additions and 192 deletions

View File

@ -142,113 +142,120 @@ fn decode_struct[T](_ T, res map[string]Any) !T {
mut typ := T{}
$if T is $struct {
$for field in T.fields {
mut skip_field := false
mut json_name := field.name
for attr in field.attrs {
if attr.contains('json: ') {
json_name = attr.replace('json: ', '')
if json_name == '-' {
skip_field = true
}
break
}
}
$if field.is_enum {
if v := res[json_name] {
typ.$(field.name) = v.int()
} else {
$if field.is_option {
typ.$(field.name) = none
if !skip_field {
$if field.is_enum {
if v := res[json_name] {
typ.$(field.name) = v.int()
} else {
$if field.is_option {
typ.$(field.name) = none
}
}
}
} $else $if field.typ is u8 {
typ.$(field.name) = res[json_name]!.u64()
} $else $if field.typ is u16 {
typ.$(field.name) = res[json_name]!.u64()
} $else $if field.typ is u32 {
typ.$(field.name) = res[json_name]!.u64()
} $else $if field.typ is u64 {
typ.$(field.name) = res[json_name]!.u64()
} $else $if field.typ is int {
typ.$(field.name) = res[json_name]!.int()
} $else $if field.typ is i8 {
typ.$(field.name) = res[json_name]!.int()
} $else $if field.typ is i16 {
typ.$(field.name) = res[json_name]!.int()
} $else $if field.typ is i32 {
typ.$(field.name) = i32(res[field.name]!.int())
} $else $if field.typ is i64 {
typ.$(field.name) = res[json_name]!.i64()
} $else $if field.typ is ?u8 {
if json_name in res {
typ.$(field.name) = ?u8(res[json_name]!.i64())
}
} $else $if field.typ is ?i8 {
if json_name in res {
typ.$(field.name) = ?i8(res[json_name]!.i64())
}
} $else $if field.typ is ?u16 {
if json_name in res {
typ.$(field.name) = ?u16(res[json_name]!.i64())
}
} $else $if field.typ is ?i16 {
if json_name in res {
typ.$(field.name) = ?i16(res[json_name]!.i64())
}
} $else $if field.typ is ?u32 {
if json_name in res {
typ.$(field.name) = ?u32(res[json_name]!.i64())
}
} $else $if field.typ is ?i32 {
if json_name in res {
typ.$(field.name) = ?i32(res[json_name]!.i64())
}
} $else $if field.typ is ?u64 {
if json_name in res {
typ.$(field.name) = ?u64(res[json_name]!.i64())
}
} $else $if field.typ is ?i64 {
if json_name in res {
typ.$(field.name) = ?i64(res[json_name]!.i64())
}
} $else $if field.typ is ?int {
if json_name in res {
typ.$(field.name) = ?int(res[json_name]!.i64())
}
} $else $if field.typ is f32 {
typ.$(field.name) = res[json_name]!.f32()
} $else $if field.typ is ?f32 {
if json_name in res {
} $else $if field.typ is u8 {
typ.$(field.name) = res[json_name]!.u64()
} $else $if field.typ is u16 {
typ.$(field.name) = res[json_name]!.u64()
} $else $if field.typ is u32 {
typ.$(field.name) = res[json_name]!.u64()
} $else $if field.typ is u64 {
typ.$(field.name) = res[json_name]!.u64()
} $else $if field.typ is int {
typ.$(field.name) = res[json_name]!.int()
} $else $if field.typ is i8 {
typ.$(field.name) = res[json_name]!.int()
} $else $if field.typ is i16 {
typ.$(field.name) = res[json_name]!.int()
} $else $if field.typ is i32 {
typ.$(field.name) = i32(res[field.name]!.int())
} $else $if field.typ is i64 {
typ.$(field.name) = res[json_name]!.i64()
} $else $if field.typ is ?u8 {
if json_name in res {
typ.$(field.name) = ?u8(res[json_name]!.i64())
}
} $else $if field.typ is ?i8 {
if json_name in res {
typ.$(field.name) = ?i8(res[json_name]!.i64())
}
} $else $if field.typ is ?u16 {
if json_name in res {
typ.$(field.name) = ?u16(res[json_name]!.i64())
}
} $else $if field.typ is ?i16 {
if json_name in res {
typ.$(field.name) = ?i16(res[json_name]!.i64())
}
} $else $if field.typ is ?u32 {
if json_name in res {
typ.$(field.name) = ?u32(res[json_name]!.i64())
}
} $else $if field.typ is ?i32 {
if json_name in res {
typ.$(field.name) = ?i32(res[json_name]!.i64())
}
} $else $if field.typ is ?u64 {
if json_name in res {
typ.$(field.name) = ?u64(res[json_name]!.i64())
}
} $else $if field.typ is ?i64 {
if json_name in res {
typ.$(field.name) = ?i64(res[json_name]!.i64())
}
} $else $if field.typ is ?int {
if json_name in res {
typ.$(field.name) = ?int(res[json_name]!.i64())
}
} $else $if field.typ is f32 {
typ.$(field.name) = res[json_name]!.f32()
}
} $else $if field.typ is f64 {
typ.$(field.name) = res[json_name]!.f64()
} $else $if field.typ is ?f64 {
if json_name in res {
} $else $if field.typ is ?f32 {
if json_name in res {
typ.$(field.name) = res[json_name]!.f32()
}
} $else $if field.typ is f64 {
typ.$(field.name) = res[json_name]!.f64()
}
} $else $if field.typ is bool {
typ.$(field.name) = res[json_name]!.bool()
} $else $if field.typ is ?bool {
if json_name in res {
} $else $if field.typ is ?f64 {
if json_name in res {
typ.$(field.name) = res[json_name]!.f64()
}
} $else $if field.typ is bool {
typ.$(field.name) = res[json_name]!.bool()
}
} $else $if field.typ is string {
typ.$(field.name) = res[json_name]!.str()
} $else $if field.typ is ?string {
if json_name in res {
} $else $if field.typ is ?bool {
if json_name in res {
typ.$(field.name) = res[json_name]!.bool()
}
} $else $if field.typ is string {
typ.$(field.name) = res[json_name]!.str()
}
} $else $if field.typ is time.Time {
typ.$(field.name) = res[json_name]!.to_time()!
} $else $if field.typ is ?time.Time {
if json_name in res {
} $else $if field.typ is ?string {
if json_name in res {
typ.$(field.name) = res[json_name]!.str()
}
} $else $if field.typ is time.Time {
typ.$(field.name) = res[json_name]!.to_time()!
} $else $if field.typ is ?time.Time {
if json_name in res {
typ.$(field.name) = res[json_name]!.to_time()!
}
} $else $if field.is_array {
} $else $if field.is_struct {
typ.$(field.name) = decode_struct(typ.$(field.name), res[field.name]!.as_map())!
} $else $if field.is_alias {
} $else $if field.is_map {
} $else {
return error("The type of `${field.name}` can't be decoded. Please open an issue at https://github.com/vlang/v/issues/new/choose")
}
} $else $if field.is_array {
} $else $if field.is_struct {
typ.$(field.name) = decode_struct(typ.$(field.name), res[field.name]!.as_map())!
} $else $if field.is_alias {
} $else $if field.is_map {
} $else {
return error("The type of `${field.name}` can't be decoded. Please open an issue at https://github.com/vlang/v/issues/new/choose")
}
}
} $else $if T is $map {

View File

@ -187,135 +187,147 @@ fn (e &Encoder) encode_struct[U](val U, level int, mut buf []u8) ! {
}
$for field in U.fields {
mut ignore_field := false
mut skip_field := false
value := val.$(field.name)
is_nil := val.$(field.name).str() == '&nil'
mut json_name := ''
for attr in field.attrs {
if attr.contains('json: ') {
json_name = attr.replace('json: ', '')
if json_name == '-' {
ignore_field = true
skip_field = true
}
break
}
}
$if value is $option {
workaround := val.$(field.name)
if workaround != none { // smartcast
e.encode_newline(level, mut buf)!
if json_name != '' {
e.encode_string(json_name, mut buf)!
} else {
e.encode_string(field.name, mut buf)!
}
buf << json2.colon_rune
if e.newline != 0 {
buf << ` `
}
e.encode_value_with_level(value, level, mut buf)!
} else {
ignore_field = true
}
} $else {
is_none := val.$(field.name).str() == 'unknown sum type value' // assert json.encode(StructType[SumTypes]{}) == '{}'
if !is_none && !is_nil {
e.encode_newline(level, mut buf)!
if json_name != '' {
e.encode_string(json_name, mut buf)!
} else {
e.encode_string(field.name, mut buf)!
}
buf << json2.colon_rune
if e.newline != 0 {
buf << ` `
}
}
$if field.indirections != 0 {
if val.$(field.name) != unsafe { nil } {
$if field.indirections == 1 {
e.encode_value_with_level(*val.$(field.name), level + 1, mut buf)!
if skip_field {
i++
fields_len--
} else {
$if value is $option {
workaround := val.$(field.name)
if workaround != none { // smartcast
e.encode_newline(level, mut buf)!
if json_name != '' {
e.encode_string(json_name, mut buf)!
} else {
e.encode_string(field.name, mut buf)!
}
$if field.indirections == 2 {
e.encode_value_with_level(**val.$(field.name), level + 1, mut
buf)!
buf << json2.colon_rune
if e.newline != 0 {
buf << ` `
}
$if field.indirections == 3 {
e.encode_value_with_level(***val.$(field.name), level + 1, mut
buf)!
e.encode_value_with_level(value, level, mut buf)!
} else {
ignore_field = true
}
} $else {
is_none := val.$(field.name).str() == 'unknown sum type value' // assert json.encode(StructType[SumTypes]{}) == '{}'
if !is_none && !is_nil {
e.encode_newline(level, mut buf)!
if json_name != '' {
e.encode_string(json_name, mut buf)!
} else {
e.encode_string(field.name, mut buf)!
}
buf << json2.colon_rune
if e.newline != 0 {
buf << ` `
}
}
} $else $if field.typ is string {
e.encode_string(val.$(field.name).str(), mut buf)!
} $else $if field.typ is time.Time {
str_value := val.$(field.name).format_rfc3339()
buf << json2.quote_rune
unsafe { buf.push_many(str_value.str, str_value.len) }
buf << json2.quote_rune
} $else $if field.typ is bool {
if value {
unsafe { buf.push_many(json2.true_in_string.str, json2.true_in_string.len) }
} else {
unsafe { buf.push_many(json2.false_in_string.str, json2.false_in_string.len) }
}
} $else $if field.typ in [$float, $int] {
str_value := val.$(field.name).str()
unsafe { buf.push_many(str_value.str, str_value.len) }
} $else $if field.is_array {
// TODO - replace for `field.typ is $array`
e.encode_array(value, level + 1, mut buf)!
} $else $if field.typ is $array {
// e.encode_array(value, level + 1, mut buf)! // FIXME - error: could not infer generic type `U` in call to `encode_array`
} $else $if field.typ is $struct {
e.encode_struct(value, level + 1, mut buf)!
} $else $if field.is_map {
e.encode_map(value, level + 1, mut buf)!
} $else $if field.is_enum {
// TODO - replace for `field.typ is $enum`
// str_value := int(val.$(field.name)).str()
// unsafe { buf.push_many(str_value.str, str_value.len) }
e.encode_value_with_level(val.$(field.name), level + 1, mut buf)!
} $else $if field.typ is $enum {
} $else $if field.typ is $sumtype {
field_value := val.$(field.name)
if field_value.str() != 'unknown sum type value' {
$for v in field_value.variants {
if field_value is v {
e.encode_value_with_level(field_value, level, mut buf)!
$if field.indirections != 0 {
if val.$(field.name) != unsafe { nil } {
$if field.indirections == 1 {
e.encode_value_with_level(*val.$(field.name), level + 1, mut
buf)!
}
$if field.indirections == 2 {
e.encode_value_with_level(**val.$(field.name), level + 1, mut
buf)!
}
$if field.indirections == 3 {
e.encode_value_with_level(***val.$(field.name), level + 1, mut
buf)!
}
}
}
} $else $if field.typ is $alias {
$if field.unaliased_typ is string {
} $else $if field.typ is string {
e.encode_string(val.$(field.name).str(), mut buf)!
} $else $if field.unaliased_typ is time.Time {
parsed_time := time.parse(val.$(field.name).str()) or { time.Time{} }
e.encode_string(parsed_time.format_rfc3339(), mut buf)!
} $else $if field.unaliased_typ is bool {
if val.$(field.name).str() == json2.true_in_string {
} $else $if field.typ is time.Time {
str_value := val.$(field.name).format_rfc3339()
buf << json2.quote_rune
unsafe { buf.push_many(str_value.str, str_value.len) }
buf << json2.quote_rune
} $else $if field.typ is bool {
if value {
unsafe { buf.push_many(json2.true_in_string.str, json2.true_in_string.len) }
} else {
unsafe { buf.push_many(json2.false_in_string.str, json2.false_in_string.len) }
}
} $else $if field.unaliased_typ in [$float, $int] {
} $else $if field.typ in [$float, $int] {
str_value := val.$(field.name).str()
unsafe { buf.push_many(str_value.str, str_value.len) }
} $else $if field.unaliased_typ is $array {
// TODO
} $else $if field.unaliased_typ is $struct {
} $else $if field.is_array {
// TODO - replace for `field.typ is $array`
e.encode_array(value, level + 1, mut buf)!
} $else $if field.typ is $array {
// e.encode_array(value, level + 1, mut buf)! // FIXME - error: could not infer generic type `U` in call to `encode_array`
} $else $if field.typ is $struct {
e.encode_struct(value, level + 1, mut buf)!
} $else $if field.unaliased_typ is $enum {
// TODO
} $else $if field.unaliased_typ is $sumtype {
// TODO
} $else $if field.is_map {
e.encode_map(value, level + 1, mut buf)!
} $else $if field.is_enum {
// TODO - replace for `field.typ is $enum`
// str_value := int(val.$(field.name)).str()
// unsafe { buf.push_many(str_value.str, str_value.len) }
e.encode_value_with_level(val.$(field.name), level + 1, mut buf)!
} $else $if field.typ is $enum {
} $else $if field.typ is $sumtype {
field_value := val.$(field.name)
if field_value.str() != 'unknown sum type value' {
$for v in field_value.variants {
if field_value is v {
e.encode_value_with_level(field_value, level, mut buf)!
}
}
}
} $else $if field.typ is $alias {
$if field.unaliased_typ is string {
e.encode_string(val.$(field.name).str(), mut buf)!
} $else $if field.unaliased_typ is time.Time {
parsed_time := time.parse(val.$(field.name).str()) or { time.Time{} }
e.encode_string(parsed_time.format_rfc3339(), mut buf)!
} $else $if field.unaliased_typ is bool {
if val.$(field.name).str() == json2.true_in_string {
unsafe { buf.push_many(json2.true_in_string.str, json2.true_in_string.len) }
} else {
unsafe { buf.push_many(json2.false_in_string.str, json2.false_in_string.len) }
}
} $else $if field.unaliased_typ in [$float, $int] {
str_value := val.$(field.name).str()
unsafe { buf.push_many(str_value.str, str_value.len) }
} $else $if field.unaliased_typ is $array {
// TODO
} $else $if field.unaliased_typ is $struct {
e.encode_struct(value, level + 1, mut buf)!
} $else $if field.unaliased_typ is $enum {
// TODO
} $else $if field.unaliased_typ is $sumtype {
// TODO
} $else {
return error('the alias ${typeof(val).name} cannot be encoded')
}
} $else {
return error('the alias ${typeof(val).name} cannot be encoded')
return error('type ${typeof(val).name} cannot be array encoded')
}
} $else {
return error('type ${typeof(val).name} cannot be array encoded')
}
}

View File

@ -45,6 +45,35 @@ mut:
val &T
}
struct StructTypeSkippedFields[T] {
mut:
val T @[json: '-']
val1 T
val2 T @[json: '-']
val3 T
}
struct StructTypeSkippedFields2[T] {
mut:
val T
val1 T @[json: '-']
val2 T
val3 T @[json: '-']
}
struct StructTypeSkippedFields3[T] {
mut:
val T @[json: '-']
val1 T @[json: '-']
val2 T @[json: '-']
val3 T @[json: '-']
}
struct StructTypeSkippedField4 {
mut:
val map[string]string @[json: '-']
}
fn test_types() {
assert json.decode[StructType[string]]('{"val": ""}')!.val == ''
assert json.decode[StructType[string]]('{"val": "0"}')!.val == '0'
@ -126,3 +155,38 @@ fn test_types() {
assert true
}
}
fn test_skipped_fields() {
if x := json.decode[StructTypeSkippedFields[int]]('{"val":10,"val1":10,"val2":10,"val3":10}') {
assert x.val == 0
assert x.val1 == 10
assert x.val2 == 0
assert x.val3 == 10
} else {
assert false
}
if x := json.decode[StructTypeSkippedFields2[int]]('{"val":10,"val1":10,"val2":10,"val3":10}') {
assert x.val == 10
assert x.val1 == 0
assert x.val2 == 10
assert x.val3 == 0
} else {
assert false
}
if x := json.decode[StructTypeSkippedFields3[int]]('{"val":10,"val1":10,"val2":10,"val3":10}') {
assert x.val == 0
assert x.val1 == 0
assert x.val2 == 0
assert x.val3 == 0
} else {
assert false
}
if x := json.decode[StructTypeSkippedField4]('{"val":{"a":"b"}}') {
assert x.val.len == 0
} else {
assert false
}
}

View File

@ -44,6 +44,30 @@ mut:
val &T
}
struct StructTypeSkippedFields[T] {
mut:
val T @[json: '-']
val1 T
val2 T @[json: '-']
val3 T
}
struct StructTypeSkippedFields2[T] {
mut:
val T
val1 T @[json: '-']
val2 T
val3 T @[json: '-']
}
struct StructTypeSkippedFields3[T] {
mut:
val T @[json: '-']
val1 T @[json: '-']
val2 T @[json: '-']
val3 T @[json: '-']
}
fn test_types() {
assert json.encode(StructType[string]{}) == '{"val":""}'
assert json.encode(StructType[string]{ val: '' }) == '{"val":""}'
@ -211,6 +235,29 @@ fn test_option_array() {
// }) == '{"val":[[0,1],[0,2,3],[2],[5,1]]}'
}
fn test_skipped_fields() {
assert json.encode(StructTypeSkippedFields[string]{
val: ''
val1: ''
val2: ''
val3: ''
}) == '{"val1":"","val3":""}'
assert json.encode(StructTypeSkippedFields2[string]{
val: ''
val1: ''
val2: ''
val3: ''
}) == '{"val":"","val2":""}'
assert json.encode(StructTypeSkippedFields3[string]{
val: ''
val1: ''
val2: ''
val3: ''
}) == '{}'
}
fn test_alias() {
assert json.encode(StructType[StringAlias]{}) == '{"val":""}'
assert json.encode(StructType[StringAlias]{ val: '' }) == '{"val":""}'