summary refs log tree commit diff stats
path: root/lib/pure/json.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/json.nim')
-rw-r--r--lib/pure/json.nim780
1 files changed, 420 insertions, 360 deletions
diff --git a/lib/pure/json.nim b/lib/pure/json.nim
index 048ed2797..53fa7553a 100644
--- a/lib/pure/json.nim
+++ b/lib/pure/json.nim
@@ -41,13 +41,14 @@
 ## For a `JsonNode` who's kind is `JObject`, you can access its fields using
 ## the `[]` operator. The following example shows how to do this:
 ##
-## .. code-block:: Nim
+##   ```Nim
 ##   import std/json
 ##
 ##   let jsonNode = parseJson("""{"key": 3.14}""")
 ##
 ##   doAssert jsonNode.kind == JObject
 ##   doAssert jsonNode["key"].kind == JFloat
+##   ```
 ##
 ## Reading values
 ## --------------
@@ -62,12 +63,13 @@
 ##
 ## To retrieve the value of `"key"` you can do the following:
 ##
-## .. code-block:: Nim
+##   ```Nim
 ##   import std/json
 ##
 ##   let jsonNode = parseJson("""{"key": 3.14}""")
 ##
 ##   doAssert jsonNode["key"].getFloat() == 3.14
+##   ```
 ##
 ## **Important:** The `[]` operator will raise an exception when the
 ## specified field does not exist.
@@ -79,7 +81,7 @@
 ## when the field is not found. The `get`-family of procedures will return a
 ## type's default value when called on `nil`.
 ##
-## .. code-block:: Nim
+##   ```Nim
 ##   import std/json
 ##
 ##   let jsonNode = parseJson("{}")
@@ -88,6 +90,7 @@
 ##   doAssert jsonNode{"nope"}.getFloat() == 0
 ##   doAssert jsonNode{"nope"}.getStr() == ""
 ##   doAssert jsonNode{"nope"}.getBool() == false
+##   ```
 ##
 ## Using default values
 ## --------------------
@@ -95,7 +98,7 @@
 ## The `get`-family helpers also accept an additional parameter which allow
 ## you to fallback to a default value should the key's values be `null`:
 ##
-## .. code-block:: Nim
+##   ```Nim
 ##   import std/json
 ##
 ##   let jsonNode = parseJson("""{"key": 3.14, "key2": null}""")
@@ -103,6 +106,7 @@
 ##   doAssert jsonNode["key"].getFloat(6.28) == 3.14
 ##   doAssert jsonNode["key2"].getFloat(3.14) == 3.14
 ##   doAssert jsonNode{"nope"}.getFloat(3.14) == 3.14 # note the {}
+##   ```
 ##
 ## Unmarshalling
 ## -------------
@@ -113,7 +117,7 @@
 ## Note: Use `Option <options.html#Option>`_ for keys sometimes missing in json
 ## responses, and backticks around keys with a reserved keyword as name.
 ##
-## .. code-block:: Nim
+##   ```Nim
 ##   import std/json
 ##   import std/options
 ##
@@ -127,6 +131,7 @@
 ##   let user = to(userJson, User)
 ##   if user.`type`.isSome():
 ##     assert user.`type`.get() != "robot"
+##   ```
 ##
 ## Creating JSON
 ## =============
@@ -134,7 +139,7 @@
 ## This module can also be used to comfortably create JSON using the `%*`
 ## operator:
 ##
-## .. code-block:: nim
+##   ```nim
 ##   import std/json
 ##
 ##   var hisName = "John"
@@ -148,6 +153,7 @@
 ##   var j2 = %* {"name": "Isaac", "books": ["Robot Dreams"]}
 ##   j2["details"] = %* {"age":35, "pi":3.1415}
 ##   echo j2
+##   ```
 ##
 ## See also: std/jsonutils for hookable json serialization/deserialization
 ## of arbitrary types.
@@ -159,12 +165,14 @@ runnableExamples:
     a1, a2, a0, a3, a4: int
   doAssert $(%* Foo()) == """{"a1":0,"a2":0,"a0":0,"a3":0,"a4":0}"""
 
-import
-  std/[hashes, tables, strutils, lexbase, streams, macros, parsejson]
+import std/[hashes, tables, strutils, lexbase, streams, macros, parsejson]
 
 import std/options # xxx remove this dependency using same approach as https://github.com/nim-lang/Nim/pull/14563
 import std/private/since
 
+when defined(nimPreviewSlimSystem):
+  import std/[syncio, assertions, formatfloat]
+
 export
   tables.`$`
 
@@ -203,6 +211,8 @@ type
     of JArray:
       elems*: seq[JsonNode]
 
+const DepthLimit = 1000
+
 proc newJString*(s: string): JsonNode =
   ## Creates a new `JString JsonNode`.
   result = JsonNode(kind: JString, str: s)
@@ -214,10 +224,6 @@ proc newJRawNumber(s: string): JsonNode =
   ## to the string representation without the quotes.
   result = JsonNode(kind: JString, str: s, isUnquoted: true)
 
-proc newJStringMove(s: string): JsonNode =
-  result = JsonNode(kind: JString)
-  shallowCopy(result.str, s)
-
 proc newJInt*(n: BiggestInt): JsonNode =
   ## Creates a new `JInt JsonNode`.
   result = JsonNode(kind: JInt, num: n)
@@ -334,7 +340,16 @@ proc `%`*(n: BiggestInt): JsonNode =
 
 proc `%`*(n: float): JsonNode =
   ## Generic constructor for JSON data. Creates a new `JFloat JsonNode`.
-  result = JsonNode(kind: JFloat, fnum: n)
+  runnableExamples:
+    assert $(%[NaN, Inf, -Inf, 0.0, -0.0, 1.0, 1e-2]) == """["nan","inf","-inf",0.0,-0.0,1.0,0.01]"""
+    assert (%NaN).kind == JString
+    assert (%0.0).kind == JFloat
+  # for those special cases, we could also have used `newJRawNumber` but then
+  # it would've been inconsisten with the case of `parseJson` vs `%` for representing them.
+  if n != n: newJString("nan")
+  elif n == Inf: newJString("inf")
+  elif n == -Inf: newJString("-inf")
+  else: JsonNode(kind: JFloat, fnum: n)
 
 proc `%`*(b: bool): JsonNode =
   ## Generic constructor for JSON data. Creates a new `JBool JsonNode`.
@@ -429,7 +444,7 @@ macro `%*`*(x: untyped): untyped =
   ## `%` for every element.
   result = toJsonImpl(x)
 
-proc `==`*(a, b: JsonNode): bool =
+proc `==`*(a, b: JsonNode): bool {.noSideEffect, raises: [].} =
   ## Check two nodes for equality
   if a.isNil:
     if b.isNil: return true
@@ -449,19 +464,25 @@ proc `==`*(a, b: JsonNode): bool =
     of JNull:
       result = true
     of JArray:
-      result = a.elems == b.elems
+      {.cast(raises: []).}: # bug #19303
+        result = a.elems == b.elems
     of JObject:
       # we cannot use OrderedTable's equality here as
       # the order does not matter for equality here.
       if a.fields.len != b.fields.len: return false
       for key, val in a.fields:
         if not b.fields.hasKey(key): return false
-        if b.fields[key] != val: return false
+        {.cast(raises: []).}:
+          when defined(nimHasEffectsOf):
+            {.noSideEffect.}:
+              if b.fields[key] != val: return false
+          else:
+            if b.fields[key] != val: return false
       result = true
 
 proc hash*(n: OrderedTable[string, JsonNode]): Hash {.noSideEffect.}
 
-proc hash*(n: JsonNode): Hash =
+proc hash*(n: JsonNode): Hash {.noSideEffect.} =
   ## Compute the hash for a JSON node
   case n.kind
   of JArray:
@@ -523,6 +544,24 @@ proc `[]`*(node: JsonNode, index: BackwardsIndex): JsonNode {.inline, since: (1,
 
   `[]`(node, node.len - int(index))
 
+proc `[]`*[U, V](a: JsonNode, x: HSlice[U, V]): JsonNode =
+  ## Slice operation for JArray.
+  ##
+  ## Returns the inclusive range `[a[x.a], a[x.b]]`:
+  runnableExamples:
+    import std/json
+    let arr = %[0,1,2,3,4,5]
+    doAssert arr[2..4] == %[2,3,4]
+    doAssert arr[2..^2] == %[2,3,4]
+    doAssert arr[^4..^2] == %[2,3,4]
+
+  assert(a.kind == JArray)
+  result = newJArray()
+  let xa = (when x.a is BackwardsIndex: a.len - int(x.a) else: int(x.a))
+  let L = (when x.b is BackwardsIndex: a.len - int(x.b) else: int(x.b)) - xa + 1
+  for i in 0..<L:
+    result.add(a[i + xa])
+
 proc hasKey*(node: JsonNode, key: string): bool =
   ## Checks if `key` exists in `node`.
   assert(node.kind == JObject)
@@ -595,7 +634,7 @@ proc delete*(obj: JsonNode, key: string) =
   obj.fields.del(key)
 
 proc copy*(p: JsonNode): JsonNode =
-  ## Performs a deep copy of `a`.
+  ## Performs a deep copy of `p`.
   case p.kind
   of JString:
     result = newJString(p.str)
@@ -663,6 +702,47 @@ proc escapeJson*(s: string): string =
   result = newStringOfCap(s.len + s.len shr 3)
   escapeJson(s, result)
 
+proc toUgly*(result: var string, node: JsonNode) =
+  ## Converts `node` to its JSON Representation, without
+  ## regard for human readability. Meant to improve `$` string
+  ## conversion performance.
+  ##
+  ## JSON representation is stored in the passed `result`
+  ##
+  ## This provides higher efficiency than the `pretty` procedure as it
+  ## does **not** attempt to format the resulting JSON to make it human readable.
+  var comma = false
+  case node.kind:
+  of JArray:
+    result.add "["
+    for child in node.elems:
+      if comma: result.add ","
+      else: comma = true
+      result.toUgly child
+    result.add "]"
+  of JObject:
+    result.add "{"
+    for key, value in pairs(node.fields):
+      if comma: result.add ","
+      else: comma = true
+      key.escapeJson(result)
+      result.add ":"
+      result.toUgly value
+    result.add "}"
+  of JString:
+    if node.isUnquoted:
+      result.add node.str
+    else:
+      escapeJson(node.str, result)
+  of JInt:
+    result.addInt(node.num)
+  of JFloat:
+    result.addFloat(node.fnum)
+  of JBool:
+    result.add(if node.bval: "true" else: "false")
+  of JNull:
+    result.add "null"
+
 proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
               lstArr = false, currIndent = 0) =
   case node.kind
@@ -690,10 +770,7 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true,
       result.add("{}")
   of JString:
     if lstArr: result.indent(currIndent)
-    if node.isUnquoted:
-      result.add node.str
-    else:
-      escapeJson(node.str, result)
+    toUgly(result, node)
   of JInt:
     if lstArr: result.indent(currIndent)
     result.addInt(node.num)
@@ -744,47 +821,6 @@ proc pretty*(node: JsonNode, indent = 2): string =
   result = ""
   toPretty(result, node, indent)
 
-proc toUgly*(result: var string, node: JsonNode) =
-  ## Converts `node` to its JSON Representation, without
-  ## regard for human readability. Meant to improve `$` string
-  ## conversion performance.
-  ##
-  ## JSON representation is stored in the passed `result`
-  ##
-  ## This provides higher efficiency than the `pretty` procedure as it
-  ## does **not** attempt to format the resulting JSON to make it human readable.
-  var comma = false
-  case node.kind:
-  of JArray:
-    result.add "["
-    for child in node.elems:
-      if comma: result.add ","
-      else: comma = true
-      result.toUgly child
-    result.add "]"
-  of JObject:
-    result.add "{"
-    for key, value in pairs(node.fields):
-      if comma: result.add ","
-      else: comma = true
-      key.escapeJson(result)
-      result.add ":"
-      result.toUgly value
-    result.add "}"
-  of JString:
-    if node.isUnquoted:
-      result.add node.str
-    else:
-      node.str.escapeJson(result)
-  of JInt:
-    result.addInt(node.num)
-  of JFloat:
-    result.addFloat(node.fnum)
-  of JBool:
-    result.add(if node.bval: "true" else: "false")
-  of JNull:
-    result.add "null"
-
 proc `$`*(node: JsonNode): string =
   ## Converts `node` to its JSON Representation on one line.
   result = newStringOfCap(node.len shl 1)
@@ -822,13 +858,17 @@ iterator mpairs*(node: var JsonNode): tuple[key: string, val: var JsonNode] =
   for key, val in mpairs(node.fields):
     yield (key, val)
 
-proc parseJson(p: var JsonParser; rawIntegers, rawFloats: bool): JsonNode =
+proc parseJson(p: var JsonParser; rawIntegers, rawFloats: bool, depth = 0): JsonNode =
   ## Parses JSON from a JSON Parser `p`.
   case p.tok
   of tkString:
     # we capture 'p.a' here, so we need to give it a fresh buffer afterwards:
-    result = newJStringMove(p.a)
-    p.a = ""
+    when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc):
+      result = JsonNode(kind: JString, str: move p.a)
+    else:
+      result = JsonNode(kind: JString)
+      shallowCopy(result.str, p.a)
+      p.a = ""
     discard getTok(p)
   of tkInt:
     if rawIntegers:
@@ -858,6 +898,8 @@ proc parseJson(p: var JsonParser; rawIntegers, rawFloats: bool): JsonNode =
     result = newJNull()
     discard getTok(p)
   of tkCurlyLe:
+    if depth > DepthLimit:
+      raiseParseErr(p, "}")
     result = newJObject()
     discard getTok(p)
     while p.tok != tkCurlyRi:
@@ -866,16 +908,18 @@ proc parseJson(p: var JsonParser; rawIntegers, rawFloats: bool): JsonNode =
       var key = p.a
       discard getTok(p)
       eat(p, tkColon)
-      var val = parseJson(p, rawIntegers, rawFloats)
+      var val = parseJson(p, rawIntegers, rawFloats, depth+1)
       result[key] = val
       if p.tok != tkComma: break
       discard getTok(p)
     eat(p, tkCurlyRi)
   of tkBracketLe:
+    if depth > DepthLimit:
+      raiseParseErr(p, "]")
     result = newJArray()
     discard getTok(p)
     while p.tok != tkBracketRi:
-      result.add(parseJson(p, rawIntegers, rawFloats))
+      result.add(parseJson(p, rawIntegers, rawFloats, depth+1))
       if p.tok != tkComma: break
       discard getTok(p)
     eat(p, tkBracketRi)
@@ -922,21 +966,22 @@ proc parseJson*(s: Stream, filename: string = ""; rawIntegers = false, rawFloats
 
 when defined(js):
   from std/math import `mod`
-  from std/jsffi import JSObject, `[]`, to
+  from std/jsffi import JsObject, `[]`, to
   from std/private/jsutils import getProtoName, isInteger, isSafeInteger
 
-  proc parseNativeJson(x: cstring): JSObject {.importjs: "JSON.parse(#)".}
+  proc parseNativeJson(x: cstring): JsObject {.importjs: "JSON.parse(#)".}
 
-  proc getVarType(x: JSObject): JsonNodeKind =
+  proc getVarType(x: JsObject, isRawNumber: var bool): JsonNodeKind =
     result = JNull
     case $getProtoName(x) # TODO: Implicit returns fail here.
     of "[object Array]": return JArray
     of "[object Object]": return JObject
     of "[object Number]":
-      if isInteger(x):
+      if isInteger(x) and 1.0 / cast[float](x) != -Inf: # preserve -0.0 as float
         if isSafeInteger(x):
           return JInt
         else:
+          isRawNumber = true
           return JString
       else:
         return JFloat
@@ -945,35 +990,41 @@ when defined(js):
     of "[object String]": return JString
     else: assert false
 
-  proc len(x: JSObject): int =
-    assert x.getVarType == JArray
-    asm """
+  proc len(x: JsObject): int =
+    {.emit: """
       `result` = `x`.length;
-    """
+    """.}
 
-  proc convertObject(x: JSObject): JsonNode =
-    case getVarType(x)
+  proc convertObject(x: JsObject): JsonNode =
+    var isRawNumber = false
+    case getVarType(x, isRawNumber)
     of JArray:
       result = newJArray()
       for i in 0 ..< x.len:
         result.add(x[i].convertObject())
     of JObject:
       result = newJObject()
-      asm """for (var property in `x`) {
+      {.emit: """for (var property in `x`) {
         if (`x`.hasOwnProperty(property)) {
-      """
+      """.}
+
       var nimProperty: cstring
-      var nimValue: JSObject
-      asm "`nimProperty` = property; `nimValue` = `x`[property];"
+      var nimValue: JsObject
+      {.emit: "`nimProperty` = property; `nimValue` = `x`[property];".}
       result[$nimProperty] = nimValue.convertObject()
-      asm "}}"
+      {.emit: "}}".}
     of JInt:
       result = newJInt(x.to(int))
     of JFloat:
       result = newJFloat(x.to(float))
     of JString:
       # Dunno what to do with isUnquoted here
-      result = newJString($x.to(cstring))
+      if isRawNumber:
+        var value: cstring
+        {.emit: "`value` = `x`.toString();".}
+        result = newJRawNumber($value)
+      else:
+        result = newJString($x.to(cstring))
     of JBool:
       result = newJBool(x.to(bool))
     of JNull:
@@ -1017,297 +1068,306 @@ template verifyJsonKind(node: JsonNode, kinds: set[JsonNodeKind],
     ]
     raise newException(JsonKindError, msg)
 
-when defined(nimFixedForwardGeneric):
-
-  macro isRefSkipDistinct*(arg: typed): untyped =
-    ## internal only, do not use
-    var impl = getTypeImpl(arg)
-    if impl.kind == nnkBracketExpr and impl[0].eqIdent("typeDesc"):
-      impl = getTypeImpl(impl[1])
-    while impl.kind == nnkDistinctTy:
-      impl = getTypeImpl(impl[0])
-    result = newLit(impl.kind == nnkRefTy)
-
-  # The following forward declarations don't work in older versions of Nim
-
-  # forward declare all initFromJson
-
-  proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: var string)
-  proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[S, T](dst: var array[S, T]; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[T](dst: var Table[string, T]; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[T](dst: var OrderedTable[string, T]; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[T: distinct](dst: var T; jsonNode: JsonNode; jsonPath: var string)
-  proc initFromJson[T: object|tuple](dst: var T; jsonNode: JsonNode; jsonPath: var string)
-
-  # initFromJson definitions
-
-  proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: var string) =
-    verifyJsonKind(jsonNode, {JString, JNull}, jsonPath)
-    # since strings don't have a nil state anymore, this mapping of
-    # JNull to the default string is questionable. `none(string)` and
-    # `some("")` have the same potentional json value `JNull`.
-    if jsonNode.kind == JNull:
-      dst = ""
-    else:
-      dst = jsonNode.str
-
-  proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: var string) =
-    verifyJsonKind(jsonNode, {JBool}, jsonPath)
-    dst = jsonNode.bval
-
-  proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: var string) =
-    if jsonNode == nil:
-      raise newException(KeyError, "key not found: " & jsonPath)
-    dst = jsonNode.copy
-
-  proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: var string) =
-    when T is uint|uint64:
-      case jsonNode.kind
-      of JString:
-        dst = T(parseBiggestUInt(jsonNode.str))
-      else:
-        verifyJsonKind(jsonNode, {JInt}, jsonPath)
-        dst = T(jsonNode.num)
-    else:
-      verifyJsonKind(jsonNode, {JInt}, jsonPath)
-      dst = cast[T](jsonNode.num)
+macro isRefSkipDistinct*(arg: typed): untyped =
+  ## internal only, do not use
+  var impl = getTypeImpl(arg)
+  if impl.kind == nnkBracketExpr and impl[0].eqIdent("typeDesc"):
+    impl = getTypeImpl(impl[1])
+  while impl.kind == nnkDistinctTy:
+    impl = getTypeImpl(impl[0])
+  result = newLit(impl.kind == nnkRefTy)
+
+# The following forward declarations don't work in older versions of Nim
+
+# forward declare all initFromJson
+
+proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: var string)
+proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[S, T](dst: var array[S, T]; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[T](dst: var Table[string, T]; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[T](dst: var OrderedTable[string, T]; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[T: distinct](dst: var T; jsonNode: JsonNode; jsonPath: var string)
+proc initFromJson[T: object|tuple](dst: var T; jsonNode: JsonNode; jsonPath: var string)
+
+# initFromJson definitions
+
+proc initFromJson(dst: var string; jsonNode: JsonNode; jsonPath: var string) =
+  verifyJsonKind(jsonNode, {JString, JNull}, jsonPath)
+  # since strings don't have a nil state anymore, this mapping of
+  # JNull to the default string is questionable. `none(string)` and
+  # `some("")` have the same potentional json value `JNull`.
+  if jsonNode.kind == JNull:
+    dst = ""
+  else:
+    dst = jsonNode.str
+
+proc initFromJson(dst: var bool; jsonNode: JsonNode; jsonPath: var string) =
+  verifyJsonKind(jsonNode, {JBool}, jsonPath)
+  dst = jsonNode.bval
+
+proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: var string) =
+  if jsonNode == nil:
+    raise newException(KeyError, "key not found: " & jsonPath)
+  dst = jsonNode.copy
 
-  proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
-    verifyJsonKind(jsonNode, {JInt, JFloat}, jsonPath)
+proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: var string) =
+  when T is uint|uint64 or int.sizeof == 4:
+    verifyJsonKind(jsonNode, {JInt, JString}, jsonPath)
+    case jsonNode.kind
+    of JString:
+      let x = parseBiggestUInt(jsonNode.str)
+      dst = cast[T](x)
+    else:
+      dst = T(jsonNode.num)
+  else:
+    verifyJsonKind(jsonNode, {JInt}, jsonPath)
+    dst = cast[T](jsonNode.num)
+
+proc initFromJson[T: SomeFloat](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
+  verifyJsonKind(jsonNode, {JInt, JFloat, JString}, jsonPath)
+  if jsonNode.kind == JString:
+    case jsonNode.str
+    of "nan":
+      let b = NaN
+      dst = T(b)
+      # dst = NaN # would fail some tests because range conversions would cause CT error
+      # in some cases; but this is not a hot-spot inside this branch and backend can optimize this.
+    of "inf":
+      let b = Inf
+      dst = T(b)
+    of "-inf":
+      let b = -Inf
+      dst = T(b)
+    else: raise newException(JsonKindError, "expected 'nan|inf|-inf', got " & jsonNode.str)
+  else:
     if jsonNode.kind == JFloat:
       dst = T(jsonNode.fnum)
     else:
       dst = T(jsonNode.num)
 
-  proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
-    verifyJsonKind(jsonNode, {JString}, jsonPath)
-    dst = parseEnum[T](jsonNode.getStr)
-
-  proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: var string) =
-    verifyJsonKind(jsonNode, {JArray}, jsonPath)
-    dst.setLen jsonNode.len
-    let orignalJsonPathLen = jsonPath.len
-    for i in 0 ..< jsonNode.len:
-      jsonPath.add '['
-      jsonPath.addInt i
-      jsonPath.add ']'
-      initFromJson(dst[i], jsonNode[i], jsonPath)
-      jsonPath.setLen orignalJsonPathLen
-
-  proc initFromJson[S,T](dst: var array[S,T]; jsonNode: JsonNode; jsonPath: var string) =
-    verifyJsonKind(jsonNode, {JArray}, jsonPath)
-    let originalJsonPathLen = jsonPath.len
-    for i in 0 ..< jsonNode.len:
-      jsonPath.add '['
-      jsonPath.addInt i
-      jsonPath.add ']'
-      initFromJson(dst[i.S], jsonNode[i], jsonPath) # `.S` for enum indexed arrays
-      jsonPath.setLen originalJsonPathLen
-
-  proc initFromJson[T](dst: var Table[string,T]; jsonNode: JsonNode; jsonPath: var string) =
-    dst = initTable[string, T]()
-    verifyJsonKind(jsonNode, {JObject}, jsonPath)
-    let originalJsonPathLen = jsonPath.len
-    for key in keys(jsonNode.fields):
-      jsonPath.add '.'
-      jsonPath.add key
-      initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath)
-      jsonPath.setLen originalJsonPathLen
-
-  proc initFromJson[T](dst: var OrderedTable[string,T]; jsonNode: JsonNode; jsonPath: var string) =
-    dst = initOrderedTable[string,T]()
-    verifyJsonKind(jsonNode, {JObject}, jsonPath)
-    let originalJsonPathLen = jsonPath.len
-    for key in keys(jsonNode.fields):
-      jsonPath.add '.'
-      jsonPath.add key
-      initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath)
-      jsonPath.setLen originalJsonPathLen
-
-  proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: var string) =
-    verifyJsonKind(jsonNode, {JObject, JNull}, jsonPath)
-    if jsonNode.kind == JNull:
-      dst = nil
-    else:
-      dst = new(T)
-      initFromJson(dst[], jsonNode, jsonPath)
-
-  proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: var string) =
-    if jsonNode != nil and jsonNode.kind != JNull:
-      when T is ref:
-        dst = some(new(T))
-      else:
-        dst = some(default(T))
-      initFromJson(dst.get, jsonNode, jsonPath)
-
-  macro assignDistinctImpl[T: distinct](dst: var T;jsonNode: JsonNode; jsonPath: var string) =
-    let typInst = getTypeInst(dst)
-    let typImpl = getTypeImpl(dst)
-    let baseTyp = typImpl[0]
+proc initFromJson[T: enum](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
+  verifyJsonKind(jsonNode, {JString}, jsonPath)
+  dst = parseEnum[T](jsonNode.getStr)
+
+proc initFromJson[T](dst: var seq[T]; jsonNode: JsonNode; jsonPath: var string) =
+  verifyJsonKind(jsonNode, {JArray}, jsonPath)
+  dst.setLen jsonNode.len
+  let orignalJsonPathLen = jsonPath.len
+  for i in 0 ..< jsonNode.len:
+    jsonPath.add '['
+    jsonPath.addInt i
+    jsonPath.add ']'
+    initFromJson(dst[i], jsonNode[i], jsonPath)
+    jsonPath.setLen orignalJsonPathLen
+
+proc initFromJson[S,T](dst: var array[S,T]; jsonNode: JsonNode; jsonPath: var string) =
+  verifyJsonKind(jsonNode, {JArray}, jsonPath)
+  let originalJsonPathLen = jsonPath.len
+  for i in 0 ..< jsonNode.len:
+    jsonPath.add '['
+    jsonPath.addInt i
+    jsonPath.add ']'
+    initFromJson(dst[i.S], jsonNode[i], jsonPath) # `.S` for enum indexed arrays
+    jsonPath.setLen originalJsonPathLen
+
+proc initFromJson[T](dst: var Table[string,T]; jsonNode: JsonNode; jsonPath: var string) =
+  dst = initTable[string, T]()
+  verifyJsonKind(jsonNode, {JObject}, jsonPath)
+  let originalJsonPathLen = jsonPath.len
+  for key in keys(jsonNode.fields):
+    jsonPath.add '.'
+    jsonPath.add key
+    initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath)
+    jsonPath.setLen originalJsonPathLen
+
+proc initFromJson[T](dst: var OrderedTable[string,T]; jsonNode: JsonNode; jsonPath: var string) =
+  dst = initOrderedTable[string,T]()
+  verifyJsonKind(jsonNode, {JObject}, jsonPath)
+  let originalJsonPathLen = jsonPath.len
+  for key in keys(jsonNode.fields):
+    jsonPath.add '.'
+    jsonPath.add key
+    initFromJson(mgetOrPut(dst, key, default(T)), jsonNode[key], jsonPath)
+    jsonPath.setLen originalJsonPathLen
+
+proc initFromJson[T](dst: var ref T; jsonNode: JsonNode; jsonPath: var string) =
+  verifyJsonKind(jsonNode, {JObject, JNull}, jsonPath)
+  if jsonNode.kind == JNull:
+    dst = nil
+  else:
+    dst = new(T)
+    initFromJson(dst[], jsonNode, jsonPath)
 
-    result = quote do:
+proc initFromJson[T](dst: var Option[T]; jsonNode: JsonNode; jsonPath: var string) =
+  if jsonNode != nil and jsonNode.kind != JNull:
+    when T is ref:
+      dst = some(new(T))
+    else:
+      dst = some(default(T))
+    initFromJson(dst.get, jsonNode, jsonPath)
+
+macro assignDistinctImpl[T: distinct](dst: var T;jsonNode: JsonNode; jsonPath: var string) =
+  let typInst = getTypeInst(dst)
+  let typImpl = getTypeImpl(dst)
+  let baseTyp = typImpl[0]
+
+  result = quote do:
+    initFromJson(`baseTyp`(`dst`), `jsonNode`, `jsonPath`)
+
+proc initFromJson[T: distinct](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
+  assignDistinctImpl(dst, jsonNode, jsonPath)
+
+proc detectIncompatibleType(typeExpr, lineinfoNode: NimNode) =
+  if typeExpr.kind == nnkTupleConstr:
+    error("Use a named tuple instead of: " & typeExpr.repr, lineinfoNode)
+
+proc foldObjectBody(dst, typeNode, tmpSym, jsonNode, jsonPath, originalJsonPathLen: NimNode) =
+  case typeNode.kind
+  of nnkEmpty:
+    discard
+  of nnkRecList, nnkTupleTy:
+    for it in typeNode:
+      foldObjectBody(dst, it, tmpSym, jsonNode, jsonPath, originalJsonPathLen)
+
+  of nnkIdentDefs:
+    typeNode.expectLen 3
+    let fieldSym = typeNode[0]
+    let fieldNameLit = newLit(fieldSym.strVal)
+    let fieldPathLit = newLit("." & fieldSym.strVal)
+    let fieldType = typeNode[1]
+
+    # Detecting incompatiple tuple types in `assignObjectImpl` only
+    # would be much cleaner, but the ast for tuple types does not
+    # contain usable type information.
+    detectIncompatibleType(fieldType, fieldSym)
+
+    dst.add quote do:
+      jsonPath.add `fieldPathLit`
       when nimvm:
-        # workaround #12282
-        var tmp: `baseTyp`
-        initFromJson( tmp, `jsonNode`, `jsonPath`)
-        `dst` = `typInst`(tmp)
-      else:
-        initFromJson( `baseTyp`(`dst`), `jsonNode`, `jsonPath`)
-
-  proc initFromJson[T: distinct](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
-    assignDistinctImpl(dst, jsonNode, jsonPath)
-
-  proc detectIncompatibleType(typeExpr, lineinfoNode: NimNode) =
-    if typeExpr.kind == nnkTupleConstr:
-      error("Use a named tuple instead of: " & typeExpr.repr, lineinfoNode)
-
-  proc foldObjectBody(dst, typeNode, tmpSym, jsonNode, jsonPath, originalJsonPathLen: NimNode) =
-    case typeNode.kind
-    of nnkEmpty:
-      discard
-    of nnkRecList, nnkTupleTy:
-      for it in typeNode:
-        foldObjectBody(dst, it, tmpSym, jsonNode, jsonPath, originalJsonPathLen)
-
-    of nnkIdentDefs:
-      typeNode.expectLen 3
-      let fieldSym = typeNode[0]
-      let fieldNameLit = newLit(fieldSym.strVal)
-      let fieldPathLit = newLit("." & fieldSym.strVal)
-      let fieldType = typeNode[1]
-
-      # Detecting incompatiple tuple types in `assignObjectImpl` only
-      # would be much cleaner, but the ast for tuple types does not
-      # contain usable type information.
-      detectIncompatibleType(fieldType, fieldSym)
-
-      dst.add quote do:
-        jsonPath.add `fieldPathLit`
-        when nimvm:
-          when isRefSkipDistinct(`tmpSym`.`fieldSym`):
-            # workaround #12489
-            var tmp: `fieldType`
-            initFromJson(tmp, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath`)
-            `tmpSym`.`fieldSym` = tmp
-          else:
-            initFromJson(`tmpSym`.`fieldSym`, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath`)
+        when isRefSkipDistinct(`tmpSym`.`fieldSym`):
+          # workaround #12489
+          var tmp: `fieldType`
+          initFromJson(tmp, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath`)
+          `tmpSym`.`fieldSym` = tmp
         else:
           initFromJson(`tmpSym`.`fieldSym`, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath`)
-        jsonPath.setLen `originalJsonPathLen`
-
-    of nnkRecCase:
-      let kindSym = typeNode[0][0]
-      let kindNameLit = newLit(kindSym.strVal)
-      let kindPathLit = newLit("." & kindSym.strVal)
-      let kindType = typeNode[0][1]
-      let kindOffsetLit = newLit(uint(getOffset(kindSym)))
-      dst.add quote do:
-        var kindTmp: `kindType`
-        jsonPath.add `kindPathLit`
-        initFromJson(kindTmp, `jsonNode`[`kindNameLit`], `jsonPath`)
-        jsonPath.setLen `originalJsonPathLen`
-        when defined js:
+      else:
+        initFromJson(`tmpSym`.`fieldSym`, getOrDefault(`jsonNode`,`fieldNameLit`), `jsonPath`)
+      jsonPath.setLen `originalJsonPathLen`
+
+  of nnkRecCase:
+    let kindSym = typeNode[0][0]
+    let kindNameLit = newLit(kindSym.strVal)
+    let kindPathLit = newLit("." & kindSym.strVal)
+    let kindType = typeNode[0][1]
+    let kindOffsetLit = newLit(uint(getOffset(kindSym)))
+    dst.add quote do:
+      var kindTmp: `kindType`
+      jsonPath.add `kindPathLit`
+      initFromJson(kindTmp, `jsonNode`[`kindNameLit`], `jsonPath`)
+      jsonPath.setLen `originalJsonPathLen`
+      when defined js:
+        `tmpSym`.`kindSym` = kindTmp
+      else:
+        when nimvm:
           `tmpSym`.`kindSym` = kindTmp
         else:
-          when nimvm:
-            `tmpSym`.`kindSym` = kindTmp
-          else:
-            # fuck it, assign kind field anyway
-            ((cast[ptr `kindType`](cast[uint](`tmpSym`.addr) + `kindOffsetLit`))[]) = kindTmp
-      dst.add nnkCaseStmt.newTree(nnkDotExpr.newTree(tmpSym, kindSym))
-      for i in 1 ..< typeNode.len:
-        foldObjectBody(dst, typeNode[i], tmpSym, jsonNode, jsonPath, originalJsonPathLen)
-
-    of nnkOfBranch, nnkElse:
-      let ofBranch = newNimNode(typeNode.kind)
-      for i in 0 ..< typeNode.len-1:
-        ofBranch.add copyNimTree(typeNode[i])
-      let dstInner = newNimNode(nnkStmtListExpr)
-      foldObjectBody(dstInner, typeNode[^1], tmpSym, jsonNode, jsonPath, originalJsonPathLen)
-      # resOuter now contains the inner stmtList
-      ofBranch.add dstInner
-      dst[^1].expectKind nnkCaseStmt
-      dst[^1].add ofBranch
-
-    of nnkObjectTy:
-      typeNode[0].expectKind nnkEmpty
-      typeNode[1].expectKind {nnkEmpty, nnkOfInherit}
-      if typeNode[1].kind == nnkOfInherit:
-        let base = typeNode[1][0]
-        var impl = getTypeImpl(base)
-        while impl.kind in {nnkRefTy, nnkPtrTy}:
-          impl = getTypeImpl(impl[0])
-        foldObjectBody(dst, impl, tmpSym, jsonNode, jsonPath, originalJsonPathLen)
-      let body = typeNode[2]
-      foldObjectBody(dst, body, tmpSym, jsonNode, jsonPath, originalJsonPathLen)
+          # fuck it, assign kind field anyway
+          ((cast[ptr `kindType`](cast[uint](`tmpSym`.addr) + `kindOffsetLit`))[]) = kindTmp
+    dst.add nnkCaseStmt.newTree(nnkDotExpr.newTree(tmpSym, kindSym))
+    for i in 1 ..< typeNode.len:
+      foldObjectBody(dst, typeNode[i], tmpSym, jsonNode, jsonPath, originalJsonPathLen)
+
+  of nnkOfBranch, nnkElse:
+    let ofBranch = newNimNode(typeNode.kind)
+    for i in 0 ..< typeNode.len-1:
+      ofBranch.add copyNimTree(typeNode[i])
+    let dstInner = newNimNode(nnkStmtListExpr)
+    foldObjectBody(dstInner, typeNode[^1], tmpSym, jsonNode, jsonPath, originalJsonPathLen)
+    # resOuter now contains the inner stmtList
+    ofBranch.add dstInner
+    dst[^1].expectKind nnkCaseStmt
+    dst[^1].add ofBranch
+
+  of nnkObjectTy:
+    typeNode[0].expectKind nnkEmpty
+    typeNode[1].expectKind {nnkEmpty, nnkOfInherit}
+    if typeNode[1].kind == nnkOfInherit:
+      let base = typeNode[1][0]
+      var impl = getTypeImpl(base)
+      while impl.kind in {nnkRefTy, nnkPtrTy}:
+        impl = getTypeImpl(impl[0])
+      foldObjectBody(dst, impl, tmpSym, jsonNode, jsonPath, originalJsonPathLen)
+    let body = typeNode[2]
+    foldObjectBody(dst, body, tmpSym, jsonNode, jsonPath, originalJsonPathLen)
 
-    else:
-      error("unhandled kind: " & $typeNode.kind, typeNode)
-
-  macro assignObjectImpl[T](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
-    let typeSym = getTypeInst(dst)
-    let originalJsonPathLen = genSym(nskLet, "originalJsonPathLen")
-    result = newStmtList()
-    result.add quote do:
-      let `originalJsonPathLen` = len(`jsonPath`)
-    if typeSym.kind in {nnkTupleTy, nnkTupleConstr}:
-      # both, `dst` and `typeSym` don't have good lineinfo. But nothing
-      # else is available here.
-      detectIncompatibleType(typeSym, dst)
-      foldObjectBody(result, typeSym, dst, jsonNode, jsonPath, originalJsonPathLen)
-    else:
-      foldObjectBody(result, typeSym.getTypeImpl, dst, jsonNode, jsonPath, originalJsonPathLen)
-
-  proc initFromJson[T: object|tuple](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
-    assignObjectImpl(dst, jsonNode, jsonPath)
-
-  proc to*[T](node: JsonNode, t: typedesc[T]): T =
-    ## `Unmarshals`:idx: the specified node into the object type specified.
-    ##
-    ## Known limitations:
-    ##
-    ##   * Heterogeneous arrays are not supported.
-    ##   * Sets in object variants are not supported.
-    ##   * Not nil annotations are not supported.
-    ##
-    runnableExamples:
-      let jsonNode = parseJson("""
-        {
-          "person": {
-            "name": "Nimmer",
-            "age": 21
-          },
-          "list": [1, 2, 3, 4]
-        }
-      """)
-
-      type
-        Person = object
-          name: string
-          age: int
-
-        Data = object
-          person: Person
-          list: seq[int]
-
-      var data = to(jsonNode, Data)
-      doAssert data.person.name == "Nimmer"
-      doAssert data.person.age == 21
-      doAssert data.list == @[1, 2, 3, 4]
-
-    var jsonPath = ""
-    initFromJson(result, node, jsonPath)
+  else:
+    error("unhandled kind: " & $typeNode.kind, typeNode)
+
+macro assignObjectImpl[T](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
+  let typeSym = getTypeInst(dst)
+  let originalJsonPathLen = genSym(nskLet, "originalJsonPathLen")
+  result = newStmtList()
+  result.add quote do:
+    let `originalJsonPathLen` = len(`jsonPath`)
+  if typeSym.kind in {nnkTupleTy, nnkTupleConstr}:
+    # both, `dst` and `typeSym` don't have good lineinfo. But nothing
+    # else is available here.
+    detectIncompatibleType(typeSym, dst)
+    foldObjectBody(result, typeSym, dst, jsonNode, jsonPath, originalJsonPathLen)
+  else:
+    foldObjectBody(result, typeSym.getTypeImpl, dst, jsonNode, jsonPath, originalJsonPathLen)
+
+proc initFromJson[T: object|tuple](dst: var T; jsonNode: JsonNode; jsonPath: var string) =
+  assignObjectImpl(dst, jsonNode, jsonPath)
+
+proc to*[T](node: JsonNode, t: typedesc[T]): T =
+  ## `Unmarshals`:idx: the specified node into the object type specified.
+  ##
+  ## Known limitations:
+  ##
+  ##   * Heterogeneous arrays are not supported.
+  ##   * Sets in object variants are not supported.
+  ##   * Not nil annotations are not supported.
+  ##
+  runnableExamples:
+    let jsonNode = parseJson("""
+      {
+        "person": {
+          "name": "Nimmer",
+          "age": 21
+        },
+        "list": [1, 2, 3, 4]
+      }
+    """)
+
+    type
+      Person = object
+        name: string
+        age: int
+
+      Data = object
+        person: Person
+        list: seq[int]
+
+    var data = to(jsonNode, Data)
+    doAssert data.person.name == "Nimmer"
+    doAssert data.person.age == 21
+    doAssert data.list == @[1, 2, 3, 4]
+
+  var jsonPath = ""
+  result = default(T)
+  initFromJson(result, node, jsonPath)
 
 when false:
-  import os
+  import std/os
   var s = newFileStream(paramStr(1), fmRead)
   if s == nil: quit("cannot open the file" & paramStr(1))
   var x: JsonParser