tjsonmacro.nim 12 KB

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