tjsonmacro.nim 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. discard """
  2. output: ""
  3. targets: "c js"
  4. """
  5. import json, strutils, options, tables
  6. import std/assertions
  7. # The definition of the `%` proc needs to be here, since the `% c` calls below
  8. # can only find our custom `%` proc for `Pix` if defined in global scope.
  9. type
  10. Pix = tuple[x, y: uint8, ch: uint16]
  11. proc `%`(p: Pix): JsonNode =
  12. result = %* { "x" : % p.x,
  13. "y" : % p.y,
  14. "ch" : % p.ch }
  15. proc testJson() =
  16. # Tests inspired by own use case (with some additional tests).
  17. # This should succeed.
  18. type
  19. Point[T] = object
  20. x, y: T
  21. ReplayEventKind = enum
  22. FoodAppeared, FoodEaten, DirectionChanged
  23. ReplayEvent = object
  24. time*: float
  25. case kind*: ReplayEventKind
  26. of FoodAppeared, FoodEaten:
  27. foodPos*: Point[float]
  28. case subKind*: bool
  29. of true:
  30. it: int
  31. of false:
  32. ot: float
  33. of DirectionChanged:
  34. playerPos*: float
  35. Replay = ref object
  36. events*: seq[ReplayEvent]
  37. test: int
  38. test2: string
  39. test3: bool
  40. testNil: string
  41. var x = Replay(
  42. events: @[
  43. ReplayEvent(
  44. time: 1.2345,
  45. kind: FoodEaten,
  46. foodPos: Point[float](x: 5.0, y: 1.0),
  47. subKind: true,
  48. it: 7
  49. )
  50. ],
  51. test: 18827361,
  52. test2: "hello world",
  53. test3: true,
  54. testNil: "nil"
  55. )
  56. let node = %x
  57. let y = to(node, Replay)
  58. doAssert y.events[0].time == 1.2345
  59. doAssert y.events[0].kind == FoodEaten
  60. doAssert y.events[0].foodPos.x == 5.0
  61. doAssert y.events[0].foodPos.y == 1.0
  62. doAssert y.test == 18827361
  63. doAssert y.test2 == "hello world"
  64. doAssert y.test3
  65. doAssert y.testNil == "nil"
  66. # Test for custom object variants (without an enum) and with an else branch.
  67. block:
  68. type
  69. TestVariant = object
  70. name: string
  71. case age: uint8
  72. of 2:
  73. preSchool: string
  74. of 8:
  75. primarySchool: string
  76. else:
  77. other: int
  78. var node = %{
  79. "name": %"Nim",
  80. "age": %8,
  81. "primarySchool": %"Sandtown"
  82. }
  83. var result = to(node, TestVariant)
  84. doAssert result.age == 8
  85. doAssert result.name == "Nim"
  86. doAssert result.primarySchool == "Sandtown"
  87. node = %{
  88. "name": %"⚔️Foo☢️",
  89. "age": %25,
  90. "other": %98
  91. }
  92. result = to(node, TestVariant)
  93. doAssert result.name == node["name"].getStr()
  94. doAssert result.age == node["age"].getInt().uint8
  95. doAssert result.other == node["other"].getBiggestInt()
  96. # TODO: Test object variant with set in of branch.
  97. # TODO: Should we support heterogeneous arrays?
  98. # Tests that verify the error messages for invalid data.
  99. block:
  100. type
  101. Person = object
  102. name: string
  103. age: int
  104. var node = %{
  105. "name": %"Dominik"
  106. }
  107. try:
  108. discard to(node, Person)
  109. doAssert false
  110. except KeyError as exc:
  111. doAssert("age" in exc.msg)
  112. except:
  113. doAssert false
  114. node["age"] = %false
  115. try:
  116. discard to(node, Person)
  117. doAssert false
  118. except JsonKindError as exc:
  119. doAssert("age" in exc.msg)
  120. except:
  121. doAssert false
  122. type
  123. PersonAge = enum
  124. Fifteen, Sixteen
  125. PersonCase = object
  126. name: string
  127. case age: PersonAge
  128. of Fifteen:
  129. discard
  130. of Sixteen:
  131. id: string
  132. try:
  133. discard to(node, PersonCase)
  134. doAssert false
  135. except JsonKindError as exc:
  136. doAssert("age" in exc.msg)
  137. except:
  138. doAssert false
  139. # Test the example in json module.
  140. block:
  141. let jsonNode = parseJson("""
  142. {
  143. "person": {
  144. "name": "Nimmer",
  145. "age": 21
  146. },
  147. "list": [1, 2, 3, 4]
  148. }
  149. """)
  150. type
  151. Person = object
  152. name: string
  153. age: int
  154. Data1 = object # TODO: Codegen bug when changed to ``Data``.
  155. person: Person
  156. list: seq[int]
  157. var data = to(jsonNode, Data1)
  158. doAssert data.person.name == "Nimmer"
  159. doAssert data.person.age == 21
  160. doAssert data.list == @[1, 2, 3, 4]
  161. # Test non-variant enum fields.
  162. block:
  163. type
  164. EnumType = enum
  165. Foo, Bar
  166. TestEnum = object
  167. field: EnumType
  168. var node = %{
  169. "field": %"Bar"
  170. }
  171. var result = to(node, TestEnum)
  172. doAssert result.field == Bar
  173. # Test ref type in field.
  174. block:
  175. var jsonNode = parseJson("""
  176. {
  177. "person": {
  178. "name": "Nimmer",
  179. "age": 21
  180. },
  181. "list": [1, 2, 3, 4]
  182. }
  183. """)
  184. type
  185. Person = ref object
  186. name: string
  187. age: int
  188. Data = object
  189. person: Person
  190. list: seq[int]
  191. var data = to(jsonNode, Data)
  192. doAssert data.person.name == "Nimmer"
  193. doAssert data.person.age == 21
  194. doAssert data.list == @[1, 2, 3, 4]
  195. jsonNode = parseJson("""
  196. {
  197. "person": null,
  198. "list": [1, 2, 3, 4]
  199. }
  200. """)
  201. data = to(jsonNode, Data)
  202. doAssert data.person.isNil
  203. block:
  204. type
  205. FooBar = object
  206. field: float
  207. let x = parseJson("""{ "field": 5}""")
  208. let data = to(x, FooBar)
  209. doAssert data.field == 5.0
  210. block:
  211. type
  212. BirdColor = object
  213. name: string
  214. rgb: array[3, float]
  215. type
  216. Bird = object
  217. age: int
  218. height: float
  219. name: string
  220. colors: array[2, BirdColor]
  221. var red = BirdColor(name: "red", rgb: [1.0, 0.0, 0.0])
  222. var blue = BirdColor(name: "blue", rgb: [0.0, 0.0, 1.0])
  223. var b = Bird(age: 3, height: 1.734, name: "bardo", colors: [red, blue])
  224. let jnode = %b
  225. let data = jnode.to(Bird)
  226. doAssert data == b
  227. block:
  228. type
  229. MsgBase = ref object of RootObj
  230. name*: string
  231. MsgChallenge = ref object of MsgBase
  232. challenge*: string
  233. let data = %*{"name": "foo", "challenge": "bar"}
  234. let msg = data.to(MsgChallenge)
  235. doAssert msg.name == "foo"
  236. doAssert msg.challenge == "bar"
  237. block:
  238. type
  239. Color = enum Red, Brown
  240. Thing = object
  241. animal: tuple[fur: bool, legs: int]
  242. color: Color
  243. var j = parseJson("""
  244. {"animal":{"fur":true,"legs":6},"color":"Red"}
  245. """)
  246. let parsed = to(j, Thing)
  247. doAssert parsed.animal.fur
  248. doAssert parsed.animal.legs == 6
  249. doAssert parsed.color == Red
  250. block:
  251. when not defined(js):
  252. # disable on js because of #12492
  253. type
  254. Car = object
  255. engine: tuple[name: string, capacity: float]
  256. model: string
  257. let j = """
  258. {"engine": {"name": "V8", "capacity": 5.5}, "model": "Skyline"}
  259. """
  260. var i = 0
  261. proc mulTest(): JsonNode =
  262. inc i
  263. return parseJson(j)
  264. let parsed = mulTest().to(Car)
  265. doAssert parsed.engine.name == "V8"
  266. doAssert i == 1
  267. block:
  268. # Option[T] support!
  269. type
  270. Car1 = object # TODO: Codegen bug when `Car`
  271. engine: tuple[name: string, capacity: Option[float]]
  272. model: string
  273. year: Option[int]
  274. let noYear = """
  275. {"engine": {"name": "V8", "capacity": 5.5}, "model": "Skyline"}
  276. """
  277. let noYearParsed = parseJson(noYear)
  278. let noYearDeser = to(noYearParsed, Car1)
  279. doAssert noYearDeser.engine.capacity == some(5.5)
  280. doAssert noYearDeser.year.isNone
  281. doAssert noYearDeser.engine.name == "V8"
  282. # Issue #7433
  283. type
  284. Obj2 = object
  285. n1: int
  286. n2: Option[string]
  287. n3: bool
  288. var j = %*[ { "n1": 4, "n2": "ABC", "n3": true },
  289. { "n1": 1, "n3": false },
  290. { "n1": 1, "n2": "XYZ", "n3": false } ]
  291. let jDeser = j.to(seq[Obj2])
  292. doAssert jDeser[0].n2.get() == "ABC"
  293. doAssert jDeser[1].n2.isNone()
  294. # Issue #6902
  295. type
  296. Obj = object
  297. n1: int
  298. n2: Option[int]
  299. n3: Option[string]
  300. n4: Option[bool]
  301. var j0 = parseJson("""{"n1": 1, "n2": null, "n3": null, "n4": null}""")
  302. let j0Deser = j0.to(Obj)
  303. doAssert j0Deser.n1 == 1
  304. doAssert j0Deser.n2.isNone()
  305. doAssert j0Deser.n3.isNone()
  306. doAssert j0Deser.n4.isNone()
  307. # Table[T, Y] support.
  308. block:
  309. type
  310. Friend = object
  311. name: string
  312. age: int
  313. Dynamic = object
  314. name: string
  315. friends: Table[string, Friend]
  316. let data = """
  317. {"friends": {
  318. "John": {"name": "John", "age": 35},
  319. "Elizabeth": {"name": "Elizabeth", "age": 23}
  320. }, "name": "Dominik"}
  321. """
  322. let dataParsed = parseJson(data)
  323. let dataDeser = to(dataParsed, Dynamic)
  324. doAssert dataDeser.name == "Dominik"
  325. doAssert dataDeser.friends["John"].age == 35
  326. doAssert dataDeser.friends["Elizabeth"].age == 23
  327. # JsonNode support
  328. block:
  329. type
  330. Test = object
  331. name: string
  332. fallback: JsonNode
  333. let data = """
  334. {"name": "FooBar", "fallback": 56.42}
  335. """
  336. let dataParsed = parseJson(data)
  337. let dataDeser = to(dataParsed, Test)
  338. doAssert dataDeser.name == "FooBar"
  339. doAssert dataDeser.fallback.kind == JFloat
  340. doAssert dataDeser.fallback.getFloat() == 56.42
  341. # int64, float64 etc support.
  342. block:
  343. type
  344. Test1 = object
  345. a: int8
  346. b: int16
  347. c: int32
  348. d: int64
  349. e: uint8
  350. f: uint16
  351. g: uint32
  352. h: uint64
  353. i: float32
  354. j: float64
  355. let data = """
  356. {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6, "g": 7,
  357. "h": 8, "i": 9.9, "j": 10.10}
  358. """
  359. let dataParsed = parseJson(data)
  360. let dataDeser = to(dataParsed, Test1)
  361. doAssert dataDeser.a == 1
  362. doAssert dataDeser.f == 6
  363. doAssert dataDeser.i == 9.9'f32
  364. # deserialize directly into a table
  365. block:
  366. let s = """{"a": 1, "b": 2}"""
  367. let t = parseJson(s).to(Table[string, int])
  368. when not defined(js):
  369. # For some reason on the JS backend `{"b": 2, "a": 0}` is
  370. # sometimes the value of `t`. This needs investigation. I can't
  371. # reproduce it right now in an isolated test.
  372. doAssert t["a"] == 1
  373. doAssert t["b"] == 2
  374. block:
  375. # bug #8037
  376. type
  377. Apple = distinct string
  378. String = distinct Apple
  379. Email = distinct string
  380. MyList = distinct seq[int]
  381. MyYear = distinct Option[int]
  382. MyTable = distinct Table[string, int]
  383. MyArr = distinct array[3, float]
  384. MyRef = ref object
  385. name: string
  386. MyObj = object
  387. color: int
  388. MyDistRef = distinct MyRef
  389. MyDistObj = distinct MyObj
  390. Toot = object
  391. name*: String
  392. email*: Email
  393. list: MyList
  394. year: MyYear
  395. dict: MyTable
  396. arr: MyArr
  397. person: MyDistRef
  398. distfruit: MyDistObj
  399. dog: MyRef
  400. fruit: MyObj
  401. emails: seq[String]
  402. var tJson = parseJson("""
  403. {
  404. "name":"Bongo",
  405. "email":"bongo@bingo.com",
  406. "list": [11,7,15],
  407. "year": 1975,
  408. "dict": {"a": 1, "b": 2},
  409. "arr": [1.0, 2.0, 7.0],
  410. "person": {"name": "boney"},
  411. "dog": {"name": "honey"},
  412. "fruit": {"color": 10},
  413. "distfruit": {"color": 11},
  414. "emails": ["abc", "123"]
  415. }
  416. """)
  417. var t = to(tJson, Toot)
  418. doAssert string(t.name) == "Bongo"
  419. doAssert string(t.email) == "bongo@bingo.com"
  420. doAssert seq[int](t.list) == @[11,7,15]
  421. doAssert Option[int](t.year).get() == 1975
  422. doAssert Table[string,int](t.dict)["a"] == 1
  423. doAssert Table[string,int](t.dict)["b"] == 2
  424. doAssert array[3, float](t.arr) == [1.0,2.0,7.0]
  425. doAssert MyRef(t.person).name == "boney"
  426. doAssert MyObj(t.distfruit).color == 11
  427. doAssert t.dog.name == "honey"
  428. doAssert t.fruit.color == 10
  429. doAssert seq[string](t.emails) == @["abc", "123"]
  430. block test_table:
  431. var y = parseJson("""{"a": 1, "b": 2, "c": 3}""")
  432. var u = y.to(MyTable)
  433. var v = y.to(Table[string, int])
  434. doAssert Table[string, int](u)["a"] == 1
  435. doAssert Table[string, int](u)["b"] == 2
  436. doAssert Table[string, int](u)["c"] == 3
  437. doAssert v["a"] == 1
  438. block primitive_string:
  439. const kApple = "apple"
  440. var u = newJString(kApple)
  441. var v = u.to(Email)
  442. var w = u.to(Apple)
  443. var x = u.to(String)
  444. doAssert string(v) == kApple
  445. doAssert string(w) == kApple
  446. doAssert string(x) == kApple
  447. block test_option:
  448. var u = newJInt(1137)
  449. var v = u.to(MyYear)
  450. var w = u.to(Option[int])
  451. doAssert Option[int](v).get() == 1137
  452. doAssert w.get() == 1137
  453. block test_object:
  454. var u = parseJson("""{"color": 987}""")
  455. var v = u.to(MyObj)
  456. var w = u.to(MyDistObj)
  457. doAssert v.color == 987
  458. doAssert MyObj(w).color == 987
  459. block test_ref_object:
  460. var u = parseJson("""{"name": "smith"}""")
  461. var v = u.to(MyRef)
  462. var w = u.to(MyDistRef)
  463. doAssert v.name == "smith"
  464. doAssert MyRef(w).name == "smith"
  465. block:
  466. # bug #12015
  467. type
  468. Cluster = object
  469. works: tuple[x, y: uint8, ch: uint16] # working
  470. fails: Pix # previously broken
  471. let data = (x: 123'u8, y: 53'u8, ch: 1231'u16)
  472. let c = Cluster(works: data, fails: data)
  473. let cFromJson = (% c).to(Cluster)
  474. doAssert c == cFromJson
  475. block:
  476. # bug related to #12015
  477. type
  478. PixInt = tuple[x, y, ch: int]
  479. SomePix = Pix | PixInt
  480. Cluster[T: SomePix] = seq[T]
  481. ClusterObject[T: SomePix] = object
  482. data: Cluster[T]
  483. RecoEvent[T: SomePix] = object
  484. cluster: seq[ClusterObject[T]]
  485. let data = @[(x: 123'u8, y: 53'u8, ch: 1231'u16)]
  486. var c = RecoEvent[Pix](cluster: @[ClusterObject[Pix](data: data)])
  487. let cFromJson = (% c).to(RecoEvent[Pix])
  488. doAssert c == cFromJson
  489. block:
  490. # ref objects with cycles.
  491. type
  492. Misdirection = object
  493. cycle: Cycle
  494. Cycle = ref object
  495. foo: string
  496. cycle: Misdirection
  497. let data = """
  498. {"cycle": null}
  499. """
  500. let dataParsed = parseJson(data)
  501. let dataDeser = to(dataParsed, Misdirection)
  502. block:
  503. # ref object from #12316
  504. type
  505. Foo = ref Bar
  506. Bar = object
  507. discard "null".parseJson.to Foo
  508. block:
  509. # named array #12289
  510. type Vec = array[2, int]
  511. let arr = "[1,2]".parseJson.to Vec
  512. doAssert arr == [1,2]
  513. block:
  514. # test error message in exception
  515. type
  516. MyType = object
  517. otherMember: string
  518. member: MySubType
  519. MySubType = object
  520. somethingElse: string
  521. list: seq[MyData]
  522. MyData = object
  523. value: int
  524. let jsonNode = parseJson("""
  525. {
  526. "otherMember": "otherValue",
  527. "member": {
  528. "somethingElse": "something",
  529. "list": [{"value": 1}, {"value": 2}, {}]
  530. }
  531. }
  532. """)
  533. try:
  534. let tmp = jsonNode.to(MyType)
  535. doAssert false, "this should be unreachable"
  536. except KeyError:
  537. doAssert getCurrentExceptionMsg().contains ".member.list[2].value"
  538. block:
  539. # Enum indexed array test
  540. type Test = enum
  541. one, two, three, four, five
  542. let a = [
  543. one: 300,
  544. two: 20,
  545. three: 10,
  546. four: 0,
  547. five: -10
  548. ]
  549. doAssert (%* a).to(a.typeof) == a
  550. testJson()
  551. static:
  552. testJson()