Fantom

 

//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   21 Dec 08  Brian Frank  Creation
//

class JsonTest : Test
{

//////////////////////////////////////////////////////////////////////////
// Basics
//////////////////////////////////////////////////////////////////////////

  Void testBasics()
  {
    // basic scalars
    verifyBasics("null", null)
    verifyBasics("true", true)
    verifyBasics("false", false)

    // numbers
    verifyBasics("5", 5)
    verifyBasics("-1234", -1234)
    verifyBasics("23.48", 23.48f)
    verifyBasics("2.309e23", 2.309e23f)
    verifyBasics("-5.8e-15", -5.8e-15f)

    // strings
    verifyBasics(Str<|""|>, "")
    verifyBasics(Str<|"x"|>, "x")
    verifyBasics(Str<|"ab"|>, "ab")
    verifyBasics(Str<|"hello world!"|>, "hello world!")
    verifyBasics(Str<|"\" \\ \/ \b \f \n \r \t"|>, "\" \\ / \b \f \n \r \t")
    verifyBasics(Str<|"\u00ab \u0ABC \uabcd"|>, "\u00ab \u0ABC \uabcd")

    // arrays
    verifyBasics("[]", Obj?[,])
    verifyBasics("[1]", Obj?[1])
    verifyBasics("[1,2.0]", Obj?[1,2f])
    verifyBasics("[1,2,3]", Obj?[1,2,3])
    verifyBasics("[3, 4.0, null, \"hi\"]", [3, 4.0f, null, "hi"])
    verifyBasics("[2,\n3]", Obj?[2, 3])
    verifyBasics("[2\n,3]", Obj?[2, 3])
    verifyBasics("[  2 \n , \n 3 ]", Obj?[2, 3])

    // objects
    verifyBasics(Str<|{}|>, Str:Obj?[:])
    verifyBasics(Str<|{"k":null}|>, Str:Obj?["k":null])
    verifyBasics(Str<|{"a":1, "b":2}|>, Str:Obj?["a":1, "b":2])
    verifyBasics(Str<|{"a":1, "b":2,}|>, Str:Obj?["a":1, "b":2])

    // errors
    verifyErr(ParseErr#) { JsonInStream("\"".in).readJson }
    verifyErr(ParseErr#) { JsonInStream("[".in).readJson }
    verifyErr(ParseErr#) { JsonInStream("[1".in).readJson }
    verifyErr(ParseErr#) { JsonInStream("[1,2".in).readJson }
    verifyErr(ParseErr#) { JsonInStream("{".in).readJson }
    verifyErr(ParseErr#) { JsonInStream("""{"x":""".in).readJson }
    verifyErr(ParseErr#) { JsonInStream("""{"x":4,""".in).readJson }
  }

  Void verifyBasics(Str s, Obj? expected)
  {
    // verify object stand alone
    verifyRoundtrip(expected)

    // wrap as [s]
    array := JsonInStream("[$s]".in).readJson as Obj?[]
    verifyType(array, Obj?[]#)
    verifyEq(array.size, 1)
    verifyEq(array[0], expected)
    verifyRoundtrip(array)

    // wrap as [s, s]
    array = JsonInStream("[$s,$s]".in).readJson as Obj?[]
    verifyType(array, Obj?[]#)
    verifyEq(array.size, 2)
    verifyEq(array[0], expected)
    verifyEq(array[1], expected)
    verifyRoundtrip(array)

    // wrap as {"key":s}
    map := JsonInStream("{\"key\":$s}".in).readJson as Str:Obj?
    verifyType(map, Str:Obj?#)
    verifyEq(map.size, 1)
    verifyEq(map["key"], expected)
    verifyRoundtrip(map)
  }

  Void verifyRoundtrip(Obj? obj)
  {
    str := JsonOutStream.writeJsonToStr(obj)
    roundtrip := JsonInStream(str.in).readJson
    verifyEq(obj, roundtrip)
  }

//////////////////////////////////////////////////////////////////////////
// Write
//////////////////////////////////////////////////////////////////////////

  Void testWrite()
  {
    // built-in scalars
    verifyWrite(null, Str<|null|>)
    verifyWrite(true, Str<|true|>)
    verifyWrite(false, Str<|false|>)
    verifyWrite("hi", Str<|"hi"|>)
    verifyWrite(-2.3e34f, Str<|-2.3E34|>)
    verifyWrite(34.12345d, Str<|34.12345|>)

    // list/map sanity checks
    verifyWrite([1, 2, 3], Str<|[1,2,3]|>)
    verifyWrite(["key":"val"], Str<|{"key":"val"}|>)
    verifyWrite(["key":"val\\\"ue"], Str<|{"key":"val\\\"ue"}|>)


    // simples
    verifyWrite(5min, Str<|"5min"|>)
    verifyWrite(`/some/uri/`, Str<|"/some/uri/"|>)
    verifyWrite(Time("23:45:01"), Str<|"23:45:01"|>)
    verifyWrite(Date("2009-12-21"), Str<|"2009-12-21"|>)
    verifyWrite(Month.dec, Str<|"dec"|>)
    verifyWrite(Version("3.4"), Str<|"3.4"|>)

    // serializable
    verifyWrite(SerialA(),
      Str<|{"b":true,"i":7,"f":5.0,"s":"string\n","ints":[1,2,3]}|>)

    // errors
    verifyErr(IOErr#) { verifyWrite(Buf(), "") }
    verifyErr(IOErr#) { verifyWrite(Str#.pod, "") }
  }

  Void verifyWrite(Obj? obj, Str expected)
  {
    verifyEq(JsonOutStream.writeJsonToStr(obj), expected)
  }

//////////////////////////////////////////////////////////////////////////
// Misc
//////////////////////////////////////////////////////////////////////////

  public Void testRaw()
  {
    // make raw json
    buf := StrBuf.make
    buf.add("\n{\n  \"type\"\n:\n\"Foobar\",\n \n\n\"age\"\n:\n34,    \n\n\n\n")
    buf.add("\t\"nested\"\t:  \n{\t \"ids\":[3.28, 3.14, 2.14],  \t\t\"dead\":false\n\n,")
    buf.add("\t\n \"friends\"\t:\n null\t  \n}\n\t\n}")
    str := buf.toStr

    // parse
    Str:Obj? map := JsonInStream(str.in).readJson

    // verify
    verifyEq(map["type"], "Foobar")
    verifyEq(map["age"], 34)
    inner := (Str:Obj?) map["nested"]
    verifyNotEq(inner, null)
    verifyEq(inner["dead"], false)
    verifyEq(inner["friends"], null)
    list := (List)inner["ids"]
    verifyNotEq(list, null)
    verifyEq(list.size, 3)
    verifyEq(map["friends"], null)
  }

  public Void testEscapes()
  {
    Str:Obj obj := JsonInStream(
      Str<|{
           "foo"   : "bar\nbaz",
           "bar"   : "_\r \t \u0abc \\ \/_",
           "baz"   : "\"hi\"",
           "num"   : 1234,
           "bool"  : true,
           "float" : 2.4,
           "dollar": "$100 \u00f7",
           "a\nb"  : "crazy key"
           }|>.in).readJson

    f := |->|
    {
      verifyEq(obj["foo"], "bar\nbaz")
      verifyEq(obj["bar"], "_\r \t \u0abc \\ /_")
      verifyEq(obj["baz"], Str<|"hi"|>)
      verifyEq(obj["num"], 1234)
      verifyEq(obj["bool"], true)
      verify(2.4f.approx(obj["float"]))
      verifyEq(obj["dollar"], "\$100 \u00f7")
      verifyEq(obj["a\nb"], "crazy key")
      verifyEq(obj.keys.join(","), "foo,bar,baz,num,bool,float,dollar,a\nb")
    }

    f()
    buf := Buf()
    JsonOutStream(buf.out).writeJson(obj)
    obj = JsonInStream(buf.flip.in).readJson
    f()
  }

}

**************************************************************************
** SerialA
**************************************************************************

@Serializable
internal class SerialA
{
  Bool b := true
  Int i := 7
  Float f := 5f
  Str s := "string\n"
  @Transient Int noGo := 99
  Int[] ints  := [1, 2, 3]
}