tjsonmacro.nim 15 KB

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