| 1 | import json |
| 2 | import time |
| 3 | |
| 4 | enum JobTitle { |
| 5 | manager |
| 6 | executive |
| 7 | worker |
| 8 | } |
| 9 | |
| 10 | struct Employee { |
| 11 | name string |
| 12 | age int |
| 13 | salary f32 |
| 14 | title JobTitle |
| 15 | } |
| 16 | |
| 17 | fn test_simple() { |
| 18 | x := Employee{'Peter', 28, 95000.5, .worker} |
| 19 | s := json.encode(x) |
| 20 | // eprintln('Employee x: ${s}') |
| 21 | assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":"worker"}' |
| 22 | y := json.decode(Employee, s)! |
| 23 | // eprintln('Employee y: ${y}') |
| 24 | assert y.name == 'Peter' |
| 25 | assert y.age == 28 |
| 26 | assert y.salary == 95000.5 |
| 27 | assert y.title == .worker |
| 28 | } |
| 29 | |
| 30 | const currency_id = 'cconst' |
| 31 | |
| 32 | struct Price { |
| 33 | net f64 |
| 34 | currency_id string = currency_id @[json: currencyId] |
| 35 | } |
| 36 | |
| 37 | fn test_field_with_default_expr() { |
| 38 | data := '[{"net":1},{"net":2,"currencyId":"cjson"}]' |
| 39 | prices := json.decode([]Price, data)! |
| 40 | assert prices == [Price{ |
| 41 | net: 1 |
| 42 | currency_id: 'cconst' |
| 43 | }, Price{ |
| 44 | net: 2 |
| 45 | currency_id: 'cjson' |
| 46 | }] |
| 47 | } |
| 48 | |
| 49 | fn test_decode_top_level_array() { |
| 50 | s := '[{"name":"Peter", "age": 29}, {"name":"Bob", "age":31}]' |
| 51 | x := json.decode([]Employee, s) or { panic(err) } |
| 52 | assert x.len == 2 |
| 53 | assert x[0].name == 'Peter' |
| 54 | assert x[0].age == 29 |
| 55 | assert x[1].name == 'Bob' |
| 56 | assert x[1].age == 31 |
| 57 | } |
| 58 | |
| 59 | struct Human { |
| 60 | name string |
| 61 | } |
| 62 | |
| 63 | struct Item { |
| 64 | tag string |
| 65 | } |
| 66 | |
| 67 | enum Animal { |
| 68 | dog // Will be encoded as `0` |
| 69 | cat |
| 70 | } |
| 71 | |
| 72 | type Entity = Animal | Human | Item | string | time.Time |
| 73 | |
| 74 | struct SomeGame { |
| 75 | title string |
| 76 | player Entity |
| 77 | other []Entity |
| 78 | } |
| 79 | |
| 80 | fn test_encode_decode_sumtype() { |
| 81 | t := time.now() |
| 82 | game := SomeGame{ |
| 83 | title: 'Super Mega Game' |
| 84 | player: Human{'Monke'} |
| 85 | other: [ |
| 86 | Entity(Item{'Pen'}), |
| 87 | Item{'Cookie'}, |
| 88 | Animal.cat, |
| 89 | 'Stool', |
| 90 | t, |
| 91 | ] |
| 92 | } |
| 93 | // eprintln('Game: ${game}') |
| 94 | |
| 95 | enc := json.encode(game) |
| 96 | // eprintln('Encoded Game: ${enc}') |
| 97 | |
| 98 | assert enc == '{"title":"Super Mega Game","player":{"name":"Monke","_type":"Human"},"other":[{"tag":"Pen","_type":"Item"},{"tag":"Cookie","_type":"Item"},"cat","Stool",{"_type":"Time","value":${t.unix()}}]}' |
| 99 | |
| 100 | dec := json.decode(SomeGame, enc)! |
| 101 | // eprintln('Decoded Game: ${dec}') |
| 102 | |
| 103 | assert game.title == dec.title |
| 104 | assert game.player == dec.player |
| 105 | assert (game.other[2] as Animal) == .cat |
| 106 | assert dec.other[2] == Entity('cat') |
| 107 | assert (game.other[4] as time.Time).unix() == (dec.other[4] as time.Time).unix() |
| 108 | } |
| 109 | |
| 110 | fn bar[T](payload string) !Bar { // ?T doesn't work currently |
| 111 | result := json.decode(T, payload)! |
| 112 | return result |
| 113 | } |
| 114 | |
| 115 | struct Bar { |
| 116 | x string |
| 117 | } |
| 118 | |
| 119 | fn test_generic() { |
| 120 | result := bar[Bar]('{"x":"test"}') or { Bar{} } |
| 121 | assert result.x == 'test' |
| 122 | } |
| 123 | |
| 124 | struct User2 { |
| 125 | age int |
| 126 | nums []int |
| 127 | reg_date time.Time |
| 128 | } |
| 129 | |
| 130 | struct User { |
| 131 | age int |
| 132 | nums []int |
| 133 | last_name string @[json: lastName] |
| 134 | is_registered bool @[json: IsRegistered] |
| 135 | typ int @[json: 'type'] |
| 136 | pets string @[json: 'pet_animals'; raw] |
| 137 | } |
| 138 | |
| 139 | fn test_parse_user() { |
| 140 | s := '{"age": 10, "nums": [1,2,3], "type": 1, "lastName": "Johnson", "IsRegistered": true, "pet_animals": {"name": "Bob", "animal": "Dog"}}' |
| 141 | u2 := json.decode(User2, s)! |
| 142 | // println(u2) |
| 143 | u := json.decode(User, s)! |
| 144 | // println(u) |
| 145 | assert u.age == 10 |
| 146 | assert u.last_name == 'Johnson' |
| 147 | assert u.is_registered == true |
| 148 | assert u.nums.len == 3 |
| 149 | assert u.nums[0] == 1 |
| 150 | assert u.nums[1] == 2 |
| 151 | assert u.nums[2] == 3 |
| 152 | assert u.typ == 1 |
| 153 | assert u.pets == '{"name":"Bob","animal":"Dog"}' |
| 154 | } |
| 155 | |
| 156 | fn test_encode_decode_time() { |
| 157 | user := User2{ |
| 158 | age: 25 |
| 159 | reg_date: time.new(year: 2020, month: 12, day: 22, hour: 7, minute: 23) |
| 160 | } |
| 161 | s := json.encode(user) |
| 162 | // println(s) |
| 163 | assert s.contains('"reg_date":1608621780') |
| 164 | user2 := json.decode(User2, s)! |
| 165 | assert user2.reg_date.str() == '2020-12-22 07:23:00' |
| 166 | // println(user2) |
| 167 | // println(user2.reg_date) |
| 168 | } |
| 169 | |
| 170 | fn test_decode_time_string_into_struct_field() { |
| 171 | user := json.decode(User2, '{"age": 25, "reg_date": "2001-01-01"}')! |
| 172 | assert user.reg_date.str() == '2001-01-01 00:00:00' |
| 173 | } |
| 174 | |
| 175 | fn (mut u User) foo() string { |
| 176 | return json.encode(u) |
| 177 | } |
| 178 | |
| 179 | fn test_encode_user() { |
| 180 | mut usr := User{ |
| 181 | age: 10 |
| 182 | nums: [1, 2, 3] |
| 183 | last_name: 'Johnson' |
| 184 | is_registered: true |
| 185 | typ: 0 |
| 186 | pets: 'foo' |
| 187 | } |
| 188 | expected := '{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"foo"}' |
| 189 | out := json.encode(usr) |
| 190 | // println(out) |
| 191 | assert out == expected |
| 192 | // Test json.encode on mutable pointers |
| 193 | assert usr.foo() == expected |
| 194 | } |
| 195 | |
| 196 | struct Color { |
| 197 | space string |
| 198 | point string @[raw] |
| 199 | } |
| 200 | |
| 201 | fn test_raw_json_field() { |
| 202 | color := json.decode(Color, '{"space": "YCbCr", "point": {"Y": 123}}') or { |
| 203 | // println('text') |
| 204 | return |
| 205 | } |
| 206 | assert color.point == '{"Y":123}' |
| 207 | assert color.space == 'YCbCr' |
| 208 | } |
| 209 | |
| 210 | fn test_bad_raw_json_field() { |
| 211 | color := json.decode(Color, '{"space": "YCbCr"}') or { |
| 212 | // println('text') |
| 213 | return |
| 214 | } |
| 215 | assert color.point == '' |
| 216 | assert color.space == 'YCbCr' |
| 217 | } |
| 218 | |
| 219 | struct City { |
| 220 | name string |
| 221 | } |
| 222 | |
| 223 | struct Country { |
| 224 | cities []City |
| 225 | name string |
| 226 | } |
| 227 | |
| 228 | fn test_struct_in_struct() { |
| 229 | country := json.decode(Country, |
| 230 | '{ "name": "UK", "cities": [{"name":"London"}, {"name":"Manchester"}]}')! |
| 231 | assert country.name == 'UK' |
| 232 | assert country.cities.len == 2 |
| 233 | assert country.cities[0].name == 'London' |
| 234 | assert country.cities[1].name == 'Manchester' |
| 235 | // println(country.cities) |
| 236 | } |
| 237 | |
| 238 | fn test_encode_map() { |
| 239 | expected := '{"one":1,"two":2,"three":3,"four":4}' |
| 240 | numbers := { |
| 241 | 'one': 1 |
| 242 | 'two': 2 |
| 243 | 'three': 3 |
| 244 | 'four': 4 |
| 245 | } |
| 246 | out := json.encode(numbers) |
| 247 | // println(out) |
| 248 | assert out == expected |
| 249 | assert json.encode_pretty(numbers) == '{ |
| 250 | "one": 1, |
| 251 | "two": 2, |
| 252 | "three": 3, |
| 253 | "four": 4 |
| 254 | }' |
| 255 | } |
| 256 | |
| 257 | fn test_parse_map() { |
| 258 | expected := { |
| 259 | 'one': 1 |
| 260 | 'two': 2 |
| 261 | 'three': 3 |
| 262 | 'four': 4 |
| 263 | } |
| 264 | out := json.decode(map[string]int, '{"one":1,"two":2,"three":3,"four":4}')! |
| 265 | // println(out) |
| 266 | assert out == expected |
| 267 | } |
| 268 | |
| 269 | struct Data { |
| 270 | countries []Country |
| 271 | users map[string]User |
| 272 | extra map[string]map[string]int |
| 273 | } |
| 274 | |
| 275 | fn test_nested_type() { |
| 276 | data_expected := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}' |
| 277 | data := Data{ |
| 278 | countries: [ |
| 279 | Country{ |
| 280 | name: 'UK' |
| 281 | cities: [City{'London'}, City{'Manchester'}] |
| 282 | }, |
| 283 | Country{ |
| 284 | name: 'KU' |
| 285 | cities: [City{'Donlon'}, City{'Termanches'}] |
| 286 | }, |
| 287 | ] |
| 288 | users: { |
| 289 | 'Foo': User{ |
| 290 | age: 10 |
| 291 | nums: [1, 2, 3] |
| 292 | last_name: 'Johnson' |
| 293 | is_registered: true |
| 294 | typ: 0 |
| 295 | pets: 'little foo' |
| 296 | } |
| 297 | 'Boo': User{ |
| 298 | age: 20 |
| 299 | nums: [5, 3, 1] |
| 300 | last_name: 'Smith' |
| 301 | is_registered: false |
| 302 | typ: 4 |
| 303 | pets: 'little boo' |
| 304 | } |
| 305 | } |
| 306 | extra: { |
| 307 | '2': { |
| 308 | 'n1': 2 |
| 309 | 'n2': 4 |
| 310 | 'n3': 8 |
| 311 | 'n4': 16 |
| 312 | } |
| 313 | '3': { |
| 314 | 'n1': 3 |
| 315 | 'n2': 9 |
| 316 | 'n3': 27 |
| 317 | 'n4': 81 |
| 318 | } |
| 319 | } |
| 320 | } |
| 321 | out := json.encode(data) |
| 322 | // println(out) |
| 323 | assert out == data_expected |
| 324 | data2 := json.decode(Data, data_expected)! |
| 325 | assert data2.countries.len == data.countries.len |
| 326 | for i in 0 .. 1 { |
| 327 | assert data2.countries[i].name == data.countries[i].name |
| 328 | assert data2.countries[i].cities.len == data.countries[i].cities.len |
| 329 | for j in 0 .. 1 { |
| 330 | assert data2.countries[i].cities[j].name == data.countries[i].cities[j].name |
| 331 | } |
| 332 | } |
| 333 | for key, user in data.users { |
| 334 | assert data2.users[key].age == user.age |
| 335 | assert data2.users[key].nums == user.nums |
| 336 | assert data2.users[key].last_name == user.last_name |
| 337 | assert data2.users[key].is_registered == user.is_registered |
| 338 | assert data2.users[key].typ == user.typ |
| 339 | // assert data2.users[key].pets == user.pets // TODO: FIX |
| 340 | } |
| 341 | for k, v in data.extra { |
| 342 | for k2, v2 in v { |
| 343 | assert data2.extra[k][k2] == v2 |
| 344 | } |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | struct Foo[T] { |
| 349 | pub: |
| 350 | name string |
| 351 | data T |
| 352 | } |
| 353 | |
| 354 | fn test_generic_struct() { |
| 355 | foo_int := Foo[int]{'bar', 12} |
| 356 | foo_enc := json.encode(foo_int) |
| 357 | assert foo_enc == '{"name":"bar","data":12}' |
| 358 | foo_dec := json.decode(Foo[int], foo_enc)! |
| 359 | assert foo_dec.name == 'bar' |
| 360 | assert foo_dec.data == 12 |
| 361 | } |
| 362 | |
| 363 | fn test_errors() { |
| 364 | invalid_array := fn () { |
| 365 | data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":{"name":"Donlon"},"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}' |
| 366 | json.decode(Data, data) or { |
| 367 | // println(err) |
| 368 | assert err.msg().starts_with('Json element is not an array:') |
| 369 | return |
| 370 | } |
| 371 | assert false |
| 372 | } |
| 373 | invalid_object := fn () { |
| 374 | data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":[{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}],"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}' |
| 375 | json.decode(Data, data) or { |
| 376 | // println(err) |
| 377 | assert err.msg().starts_with('Json element is not an object:') |
| 378 | return |
| 379 | } |
| 380 | assert false |
| 381 | } |
| 382 | invalid_array() |
| 383 | invalid_object() |
| 384 | } |
| 385 | |
| 386 | type ID = string |
| 387 | type GG = int |
| 388 | |
| 389 | struct Message { |
| 390 | id ID |
| 391 | ij GG |
| 392 | } |
| 393 | |
| 394 | fn test_decode_alias_struct() { |
| 395 | msg := json.decode(Message, '{"id": "118499178790780929"}')! |
| 396 | // hacky way of comparing aliased strings |
| 397 | assert msg.id.str() == '118499178790780929' |
| 398 | assert msg.ij.str() == '0' |
| 399 | } |
| 400 | |
| 401 | fn test_encode_alias_struct() { |
| 402 | expected := '{"id":"118499178790780929","ij":999998888}' |
| 403 | msg := Message{'118499178790780929', 999998888} |
| 404 | out := json.encode(msg) |
| 405 | assert out == expected |
| 406 | } |
| 407 | |
| 408 | struct List { |
| 409 | id int |
| 410 | items []string |
| 411 | } |
| 412 | |
| 413 | fn test_list() { |
| 414 | list := json.decode(List, '{"id": 1, "items": ["1", "2"]}')! |
| 415 | assert list.id == 1 |
| 416 | assert list.items == ['1', '2'] |
| 417 | } |
| 418 | |
| 419 | fn test_list_no_id() { |
| 420 | list := json.decode(List, '{"items": ["1", "2"]}')! |
| 421 | assert list.id == 0 |
| 422 | assert list.items == ['1', '2'] |
| 423 | } |
| 424 | |
| 425 | fn test_list_no_items() { |
| 426 | list := json.decode(List, '{"id": 1}')! |
| 427 | assert list.id == 1 |
| 428 | assert list.items == [] |
| 429 | } |
| 430 | |
| 431 | struct Info { |
| 432 | id int |
| 433 | items []string |
| 434 | maps map[string]string |
| 435 | } |
| 436 | |
| 437 | fn test_decode_null_object() { |
| 438 | info := json.decode(Info, '{"id": 22, "items": null, "maps": null}')! |
| 439 | assert info.id == 22 |
| 440 | assert '${info.items}' == '[]' |
| 441 | assert '${info.maps}' == '{}' |
| 442 | } |
| 443 | |
| 444 | fn test_decode_missing_maps_field() { |
| 445 | info := json.decode(Info, '{"id": 22, "items": null}')! |
| 446 | assert info.id == 22 |
| 447 | assert '${info.items}' == '[]' |
| 448 | assert '${info.maps}' == '{}' |
| 449 | } |
| 450 | |
| 451 | struct Foo2 { |
| 452 | name string |
| 453 | } |
| 454 | |
| 455 | fn test_pretty() { |
| 456 | foo := Foo2{'Bob'} |
| 457 | assert json.encode_pretty(foo) == '{ |
| 458 | "name": "Bob" |
| 459 | }' |
| 460 | } |
| 461 | |
| 462 | struct Foo3 { |
| 463 | name string |
| 464 | age int @[omitempty] |
| 465 | } |
| 466 | |
| 467 | fn test_omit_empty() { |
| 468 | foo := Foo3{'Bob', 0} |
| 469 | assert json.encode_pretty(foo) == '{ |
| 470 | "name": "Bob" |
| 471 | }' |
| 472 | // println('omitempty:') |
| 473 | // println(json.encode_pretty(foo)) |
| 474 | } |
| 475 | |
| 476 | fn test_encode_struct_expression() { |
| 477 | assert json.encode(Foo2{'Foo'}) == '{"name":"Foo"}' |
| 478 | assert json.encode_pretty(Foo2{'Bar'}) == '{ |
| 479 | "name": "Bar" |
| 480 | }' |
| 481 | } |
| 482 | |
| 483 | struct Asdasd { |
| 484 | data GamePacketData |
| 485 | } |
| 486 | |
| 487 | type GamePacketData = GPEquipItem | GPScale |
| 488 | |
| 489 | struct GPScale { |
| 490 | value f32 |
| 491 | } |
| 492 | |
| 493 | struct GPEquipItem { |
| 494 | name string |
| 495 | } |
| 496 | |
| 497 | fn create_game_packet(data &GamePacketData) string { |
| 498 | return json.encode(data) |
| 499 | } |
| 500 | |
| 501 | fn test_encode_sumtype_defined_ahead() { |
| 502 | ret := create_game_packet(&GamePacketData(GPScale{})) |
| 503 | // println(ret) |
| 504 | assert ret == '{"value":0,"_type":"GPScale"}' |
| 505 | } |
| 506 | |
| 507 | struct StByteArray { |
| 508 | ba []u8 |
| 509 | } |
| 510 | |
| 511 | fn test_byte_array() { |
| 512 | assert json.encode(StByteArray{ ba: [u8(1), 2, 3, 4, 5] }) == '{"ba":[1,2,3,4,5]}' |
| 513 | } |
| 514 | |
| 515 | struct Aa { |
| 516 | sub AliasType |
| 517 | } |
| 518 | |
| 519 | struct Bb { |
| 520 | a int |
| 521 | } |
| 522 | |
| 523 | type AliasType = Bb |
| 524 | |
| 525 | fn test_encode_alias_field() { |
| 526 | s := json.encode(Aa{ |
| 527 | sub: Bb{ |
| 528 | a: 1 |
| 529 | } |
| 530 | }) |
| 531 | assert s == '{"sub":{"a":1}}' |
| 532 | } |
| 533 | |
| 534 | struct APrice {} |
| 535 | |
| 536 | struct Association { |
| 537 | association &Association = unsafe { nil } |
| 538 | price APrice |
| 539 | } |
| 540 | |
| 541 | fn test_encoding_struct_with_pointers() { |
| 542 | value := Association{ |
| 543 | association: &Association{ |
| 544 | price: APrice{} |
| 545 | } |
| 546 | price: APrice{} |
| 547 | } |
| 548 | assert json.encode(value) == '{"association":{"price":{}},"price":{}}' |
| 549 | } |
| 550 | |
| 551 | struct ChildNullish { |
| 552 | name string |
| 553 | } |
| 554 | |
| 555 | struct NullishStruct { |
| 556 | name string |
| 557 | lastname string |
| 558 | age int |
| 559 | salary f32 |
| 560 | child ChildNullish |
| 561 | } |
| 562 | |
| 563 | struct RequiredStruct { |
| 564 | name string @[required] |
| 565 | lastname string |
| 566 | } |
| 567 | |
| 568 | fn test_required() { |
| 569 | nullish_one := json.decode(NullishStruct, |
| 570 | '{"name":"Peter", "lastname":null, "age":28,"salary":95000.5,"title":"worker"}')! |
| 571 | assert nullish_one.name == 'Peter' |
| 572 | assert nullish_one.lastname == '' |
| 573 | assert nullish_one.age == 28 |
| 574 | assert nullish_one.salary == 95000.5 |
| 575 | assert nullish_one.child.name == '' |
| 576 | |
| 577 | nullish_two := json.decode(NullishStruct, |
| 578 | '{"name":"Peter", "lastname": "Parker", "age":28,"salary":95000.5,"title":"worker"}')! |
| 579 | assert nullish_two.name == 'Peter' |
| 580 | assert nullish_two.lastname == 'Parker' |
| 581 | assert nullish_two.age == 28 |
| 582 | assert nullish_two.salary == 95000.5 |
| 583 | assert nullish_two.child.name == '' |
| 584 | |
| 585 | nullish_third := json.decode(NullishStruct, |
| 586 | '{"name":"Peter", "age":28,"salary":95000.5,"title":"worker", "child": {"name":"Nullish"}}')! |
| 587 | assert nullish_third.name == 'Peter' |
| 588 | assert nullish_third.lastname == '' |
| 589 | assert nullish_third.age == 28 |
| 590 | assert nullish_third.salary == 95000.5 |
| 591 | assert nullish_third.child.name == 'Nullish' |
| 592 | |
| 593 | required_struct := json.decode(RequiredStruct, '{"name":"Peter", "lastname": "Parker"}')! |
| 594 | assert required_struct.name == 'Peter' |
| 595 | assert required_struct.lastname == 'Parker' |
| 596 | |
| 597 | required_struct_err := json.decode(RequiredStruct, '{"name": null, "lastname": "Parker"}') or { |
| 598 | assert err.msg() == "type mismatch for field 'name', expecting `string` type, got: null" |
| 599 | RequiredStruct{ |
| 600 | name: 'Peter' |
| 601 | lastname: 'Parker' |
| 602 | } |
| 603 | } |
| 604 | } |
| 605 | |