tjsonmacro.nim 15 KB

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