This commit is contained in:
sberezin 2016-02-17 18:54:34 +03:00
Родитель b7bfcb4682
Коммит 3f1257209b
10 изменённых файлов: 305 добавлений и 271 удалений

Просмотреть файл

@ -1,7 +1,8 @@
{
"name": "Angara.SerializationJS",
"main": [ "./dist/Angara.Serialization.umd.js" ],
"version": "0.1.1",
"moduleType": "amd",
"version": "0.1.2",
"authors": [ "Microsoft Research" ],
"private": false,
"description": "Serialization and deserialization into and from JSON format with type information preserved",

Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "angara.serializationjs",
"version": "0.1.1",
"version": "0.1.2",
"license": "MIT",
"private": true,
"devDependencies": {

Просмотреть файл

@ -5,25 +5,25 @@ open Angara.Serialization
open Swensen.Unquote
open System
open Angara.Serialization.Json
open Angara.Serialization
let equalSeq<'U when 'U : comparison> (s1 : 'U seq) (s2 : 'U seq) =
Seq.zip s1 s2 |> Seq.forall (fun (e1,e2) -> e1 = e2)
let shouldRoundtrip (i : InfoSet) =
let json = Marshal(i, None)
let json = Json.Marshal(i, None)
let s = json.ToString(Newtonsoft.Json.Formatting.Indented)
printfn "%s" s
let json' = Newtonsoft.Json.Linq.JToken.Parse(s)
let i' = Unmarshal(json', None)
let i' = Json.Unmarshal(json', None)
i =? i'
let shouldSatisfy (p: InfoSet -> bool) (i : InfoSet) =
let json = Marshal(i, None)
let json = Json.Marshal(i, None)
let s = json.ToString(Newtonsoft.Json.Formatting.Indented)
printfn "%s" s
let json' = Newtonsoft.Json.Linq.JToken.Parse(s)
let i' = Unmarshal(json', None)
let i' = Json.Unmarshal(json', None)
Assert.IsTrue(p i')
[<Test>]
@ -69,7 +69,7 @@ let ``Primitive InfoSets roundtrip to JSON`` () =
InfoSet.DateTime(DateTime.MaxValue) |> shouldRoundtrip
// Guid
let guid = Guid("D5A1A767-DCE9-4C1D-B946-D1970F2A153B")
let guid = System.Guid("D5A1A767-DCE9-4C1D-B946-D1970F2A153B")
InfoSet.Guid(guid) |> shouldRoundtrip
// String
@ -209,10 +209,10 @@ let ``Array of arrays roundtrips to JSON``() =
InfoSet.DoubleArray(id);
InfoSet.DateTimeArray(idt) ] |> Seq.ofList)
let json = Marshal(arrays, None)
let json = Json.Marshal(arrays, None)
System.Diagnostics.Trace.WriteLine("Array of arrays in JSON format")
System.Diagnostics.Trace.WriteLine(json.ToString())
let si2 = Unmarshal(json, None)
let si2 = Json.Unmarshal(json, None)
match si2 with
| Seq(s) ->

Просмотреть файл

@ -6,7 +6,7 @@ open System.Runtime.InteropServices
module internal Const =
[<Literal>]
let Version = "0.1.1" // Assembly semantic version
let Version = "0.1.2" // Assembly semantic version
[<assembly: AssemblyTitle("Angara.Serialization.Json")>]
[<assembly: AssemblyDescription("Contains converters of InfoSets into and from JSON representation with type information preserved. " +

Просмотреть файл

@ -1,4 +1,4 @@
module Angara.Serialization.Json
namespace Angara.Serialization
open Angara.Serialization
@ -26,225 +26,234 @@ type internal InlineBase64Blob(s : string) =
let bytes = Convert.FromBase64String(s)
stream.Write(bytes, 0, bytes.Length)
let internal encodeDecimalArray (a : System.Decimal array) =
let len = a.Length
let buffer = Array.zeroCreate<byte>(len * 16)
a |> Array.iteri(fun i d -> let bits = Decimal.GetBits d
Buffer.BlockCopy(bits, 0, buffer, i * 16, 16))
Convert.ToBase64String(buffer)
module internal Utils =
let internal decodeDecimalArray s =
let buffer = Convert.FromBase64String(s)
let length = buffer.Length / 16
let bits = Array.zeroCreate<int> 4
[| for i in 0..length - 1 -> Buffer.BlockCopy(buffer, i * 16, bits, 0, 16)
Decimal(bits) |]
let encodeDecimalArray (a : System.Decimal array) =
let len = a.Length
let buffer = Array.zeroCreate<byte>(len * 16)
a |> Array.iteri(fun i d -> let bits = Decimal.GetBits d
Buffer.BlockCopy(bits, 0, buffer, i * 16, 16))
Convert.ToBase64String(buffer)
let decodeDecimalArray s =
let buffer = Convert.FromBase64String(s)
let length = buffer.Length / 16
let bits = Array.zeroCreate<int> 4
[| for i in 0..length - 1 -> Buffer.BlockCopy(buffer, i * 16, bits, 0, 16)
Decimal(bits) |]
let internal encode1DArray a =
let len = Buffer.ByteLength(a)
let buffer = Array.zeroCreate<byte> len
Buffer.BlockCopy(a, 0, buffer, 0, len)
Convert.ToBase64String(buffer)
let encode1DArray a =
let len = Buffer.ByteLength(a)
let buffer = Array.zeroCreate<byte> len
Buffer.BlockCopy(a, 0, buffer, 0, len)
Convert.ToBase64String(buffer)
let internal decode1DArray<'a> (s, elsize) =
let buffer = Convert.FromBase64String(s)
let length = buffer.Length / elsize
let array = Array.zeroCreate<'a> length
Buffer.BlockCopy(buffer, 0, array, 0, buffer.Length);
array
let decode1DArray<'a> (s, elsize) =
let buffer = Convert.FromBase64String(s)
let length = buffer.Length / elsize
let array = Array.zeroCreate<'a> length
Buffer.BlockCopy(buffer, 0, array, 0, buffer.Length);
array
let internal encodeNameAndType (n : string, t : string option) =
let n = n.Replace(":", "::")
match t with
| None -> n
| Some(t) -> String.Concat(n, ":", t)
let internal decodeNameAndType (name : string) =
let idx = name.LastIndexOf(':');
if idx = 0 || idx > 0 && name.[idx - 1] <> ':'
then name.Substring(0, idx).Replace("::", ":"), Some(name.Substring(idx + 1))
else name.Replace("::", ":"), None
let internal UnixEpochOrigin = DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)
let internal DateTimeMinUtc = DateTime.MinValue.ToUniversalTime()
let internal DateTimeMaxUtc = DateTime.MaxValue.ToUniversalTime()
/// Returns the number of milliseconds since 1 Jan 1970 00:00:00 UTC (Unix Epoch)
let internal DateTimeToUnixEpoch (dt:DateTime) : float =
let udt = if dt = DateTime.MinValue then DateTimeMinUtc
elif dt = DateTime.MaxValue then DateTimeMaxUtc
else dt.ToUniversalTime()
udt.Subtract(UnixEpochOrigin).TotalMilliseconds
/// Returns the DateTime instance (local time) from number of milliseconds since 1 Jan 1970 00:00:00 UTC (Unix Epoch)
let internal UnixEpochToDateTime (value: float) : DateTime =
let udt = UnixEpochOrigin.AddMilliseconds(value)
if udt = DateTimeMinUtc then DateTime.MinValue
elif udt = DateTimeMaxUtc then DateTime.MaxValue
else udt.ToLocalTime()
let rec Marshal (infoSet, writer : IBlobWriter option) =
let rec getJson (is : InfoSet, writer : IBlobWriter option) : (JToken * string option) =
let encodeMap (map : Map<string, InfoSet>) =
let result = JObject()
Map.iter (fun k v -> let jtoken, t = getJson(v,writer)
result.Add(encodeNameAndType(k, t), jtoken)) map
result :> JToken
match is with
// Primitives
| Null -> upcast JValue(null :> obj), None
| Int(i) -> upcast JValue(i), Some("int")
| UInt(i) -> upcast JValue(i), Some("uint")
| Int64(i) -> upcast JValue(i), Some("int64")
| UInt64(i) -> upcast JValue(Convert.ToBase64String(BitConverter.GetBytes(i))), Some("uint64") // JSON cannot represent entire range of UInt64 (see http://stackoverflow.com/questions/9355091/json-net-crashes-when-serializing-unsigned-integer-ulong-array)
| Decimal(d) -> let bytes = Decimal.GetBits(d) |> Array.map BitConverter.GetBytes |> Array.concat
upcast JValue(Convert.ToBase64String(bytes)), Some("decimal")
| Double(d) -> if Double.IsNaN d || Double.IsInfinity d
then upcast JValue(d), Some("double")
else upcast JValue(d), None
| String(s) -> upcast JValue(s), if s = null then Some("string") else None
| Bool(b) -> upcast JValue(b), None
| DateTime(dt) -> upcast JValue(dt), Some("datetime")
| Guid(g) -> upcast JValue(g.ToString()), Some("guid")
// Arrays of primitives
| IntArray(a) -> upcast JValue(a |> Array.ofSeq |> encode1DArray), Some("int array")
| UIntArray(a) -> upcast JValue(a |> Array.ofSeq |> encode1DArray), Some("uint array")
| Int64Array(a) -> upcast JValue(a |> Array.ofSeq |> encode1DArray), Some("int64 array")
| UInt64Array(a) -> upcast JValue(a |> Array.ofSeq |> encode1DArray), Some("uint64 array")
| DoubleArray(a) -> upcast JValue(a |> Array.ofSeq |> encode1DArray), Some("double array")
| DecimalArray(a) -> upcast JValue(a |> Array.ofSeq |> encodeDecimalArray), Some("decimal array")
| BoolArray(a) -> upcast JValue(a |> Array.ofSeq |> encode1DArray), Some("bool array")
| DateTimeArray(a) -> upcast JValue(a |> Seq.map DateTimeToUnixEpoch |> Array.ofSeq |> encode1DArray), Some("datetime array")
| StringArray(a) -> upcast JArray(a |> Array.ofSeq), if Seq.isEmpty a then Some("string array") else None
| ByteArray(a) -> upcast JValue(a |> Array.ofSeq |> encode1DArray), Some("byte array")
// Mapping
| Artefact(typeID, content) -> Marshal(content, writer), Some(typeID)
| Map(map) -> encodeMap(map), None
// Sequence
| Seq(s) -> let items = s |> Seq.map (fun i -> Marshal(i, writer))
upcast JArray(items), None
// Blobs and namespaces
| Namespace(names, infoSet) -> let result = JObject()
result.Add("name", JArray(names))
let nsc = getJson(infoSet, match writer with
| None -> None
| Some(origin) -> let mutable w = origin
for n in names do
w <- w.AddGroup(n)
Some(w))
result.Add(encodeNameAndType("content", snd nsc), fst nsc)
upcast result, Some("namespace")
| Blob(name,blob) -> let result = JObject()
result.Add("name", JValue(name))
match writer with
| Some(w) -> w.Write(name, blob)
| None -> use reader = new System.IO.BinaryReader(blob.GetStream())
result.Add("data", JValue(System.Convert.ToBase64String(reader.ReadBytes(int(reader.BaseStream.Length)))))
upcast result, Some("data")
match getJson(infoSet, writer) with
| token, None -> token
| token, Some(typeID) -> let res = JObject()
res.Add(encodeNameAndType("", Some(typeID)), token)
res :> JToken
let rec Unmarshal(token, reader) =
let rec getInfoSet (token : JToken, t : string option, reader : IBlobReader option) : InfoSet =
let decodeMap (obj : JObject) =
let mutable nts : (string * string option * InfoSet) list = List.empty
for pair in obj do
let n,t = decodeNameAndType pair.Key
nts <- (n,t,getInfoSet(pair.Value, t, reader)) :: nts
if nts.Length = 1
then
match nts.Head with
| "", _, infoSet -> infoSet
| n, _, infoSet -> InfoSet.EmptyMap.AddInfoSet(n,infoSet)
else
nts |> List.map (fun t -> let n,_,s = t in n,s) |> InfoSet.ofPairs
let encodeNameAndType (n : string, t : string option) =
let n = n.Replace(":", "::")
match t with
| None -> match token.Type with
| JTokenType.Null -> InfoSet.Null
| JTokenType.Integer -> InfoSet.Int(token.Value<int>())
| JTokenType.Float -> InfoSet.Double(token.Value<float>())
| JTokenType.String -> InfoSet.String(token.Value<string>())
| JTokenType.Boolean -> InfoSet.Bool(token.Value<bool>())
| JTokenType.Array ->
let arr = token :?> JArray
if arr.Count > 0 && arr |> Seq.forall (fun i -> i.Type = JTokenType.String)
then InfoSet.StringArray(arr |> Seq.map (fun i -> i.Value<string>()))
else InfoSet.Seq(arr |> Seq.map (fun i -> Unmarshal(i, reader)) |> List.ofSeq)
| JTokenType.Object -> token :?> JObject |> decodeMap
| _ -> failwith ("Cannot create InfoSet from JToken of type " + token.Type.ToString())
// Primitives
| Some("int") -> InfoSet.Int(token.Value<int>())
| Some("uint") -> InfoSet.UInt(token.Value<UInt32>())
| Some("int64") -> InfoSet.Int64(token.Value<Int64>())
| Some("uint64") -> InfoSet.UInt64(if token.Type = JTokenType.String then BitConverter.ToUInt64(Convert.FromBase64String(token.Value<string>()), 0)
else token.Value<UInt64>())
| Some("decimal") -> InfoSet.Decimal(if token.Type = JTokenType.String then let bytes = Convert.FromBase64String(token.Value<string>())
let ints = [|0..3|] |> Array.map (fun i -> BitConverter.ToInt32(bytes, i * 4))
Decimal(ints)
else token.Value<decimal>())
| Some("guid") -> InfoSet.Guid(Guid.Parse(token.Value<string>()))
| Some("datetime") -> InfoSet.DateTime(token.Value<DateTime>())
| Some("double") -> InfoSet.Double(token.Value<double>())
| Some("string") -> InfoSet.String(token.Value<string>())
// Arrays
| Some("string array") -> InfoSet.StringArray(token.Values<string>())
| Some("int array") -> InfoSet.IntArray(decode1DArray(token.Value<string>(), 4))
| Some("uint array") -> InfoSet.UIntArray(decode1DArray(token.Value<string>(), 4))
| Some("int64 array") -> InfoSet.Int64Array(decode1DArray(token.Value<string>(), 8))
| Some("uint64 array") -> InfoSet.UInt64Array(decode1DArray(token.Value<string>(), 8))
| Some("double array") -> InfoSet.DoubleArray(decode1DArray(token.Value<string>(), 8))
| Some("decimal array") -> InfoSet.DecimalArray(decodeDecimalArray(token.Value<string>()))
| Some("datetime array") -> InfoSet.DateTimeArray(decode1DArray<float>(token.Value<string>(), 8) |> Array.map UnixEpochToDateTime)
| Some("bool array") -> InfoSet.BoolArray(decode1DArray(token.Value<string>(), 1))
| Some("byte array") -> InfoSet.ByteArray(decode1DArray(token.Value<string>(), 1))
// Blobs
| Some("data") -> let obj = token :?> JObject
let name = obj.["name"].Value<string>()
InfoSet.Blob(name, match obj.TryGetValue("data") with
| true, data -> InlineBase64Blob(data.Value<string>()) :> IBlob
| false, _ -> match reader with
| Some(r) -> r.Read(name)
| None -> failwith ("Cannot read blob " + name + ". Reader is not supplied"))
| Some("namespace") -> let obj = token :?> JObject
let mutable names = List.empty<string>
let mutable token : JToken = null
let mutable typeID : string option = None
for pair in obj do
match decodeNameAndType pair.Key with
| "content", t -> token <- pair.Value
typeID <- t
| "name", None -> names <- List.ofSeq(pair.Value.Values<string>())
| n,_ -> failwith ("Cannot decode namespace record. Unknown field " + n)
| None -> n
| Some(t) -> String.Concat(n, ":", t)
let decodeNameAndType (name : string) =
let idx = name.LastIndexOf(':');
if idx = 0 || idx > 0 && name.[idx - 1] <> ':'
then name.Substring(0, idx).Replace("::", ":"), Some(name.Substring(idx + 1))
else name.Replace("::", ":"), None
let UnixEpochOrigin = DateTime(1970,1,1,0,0,0, DateTimeKind.Utc)
let DateTimeMinUtc = DateTime.MinValue.ToUniversalTime()
let DateTimeMaxUtc = DateTime.MaxValue.ToUniversalTime()
/// Returns the number of milliseconds since 1 Jan 1970 00:00:00 UTC (Unix Epoch)
let DateTimeToUnixEpoch (dt:DateTime) : float =
let udt = if dt = DateTime.MinValue then DateTimeMinUtc
elif dt = DateTime.MaxValue then DateTimeMaxUtc
else dt.ToUniversalTime()
udt.Subtract(UnixEpochOrigin).TotalMilliseconds
/// Returns the DateTime instance (local time) from number of milliseconds since 1 Jan 1970 00:00:00 UTC (Unix Epoch)
let UnixEpochToDateTime (value: float) : DateTime =
let udt = UnixEpochOrigin.AddMilliseconds(value)
if udt = DateTimeMinUtc then DateTime.MinValue
elif udt = DateTimeMaxUtc then DateTime.MaxValue
else udt.ToLocalTime()
type Json private () =
static member Marshal (infoSet, writer : IBlobWriter option) =
let rec getJson (is : InfoSet, writer : IBlobWriter option) : (JToken * string option) =
let encodeMap (map : Map<string, InfoSet>) =
let result = JObject()
Map.iter (fun k v -> let jtoken, t = getJson(v,writer)
result.Add(Utils.encodeNameAndType(k, t), jtoken)) map
result :> JToken
match is with
// Primitives
| Null -> upcast JValue(null :> obj), None
| Int(i) -> upcast JValue(i), Some("int")
| UInt(i) -> upcast JValue(i), Some("uint")
| Int64(i) -> upcast JValue(i), Some("int64")
| UInt64(i) -> upcast JValue(Convert.ToBase64String(BitConverter.GetBytes(i))), Some("uint64") // JSON cannot represent entire range of UInt64 (see http://stackoverflow.com/questions/9355091/json-net-crashes-when-serializing-unsigned-integer-ulong-array)
| Decimal(d) -> let bytes = Decimal.GetBits(d) |> Array.map BitConverter.GetBytes |> Array.concat
upcast JValue(Convert.ToBase64String(bytes)), Some("decimal")
| Double(d) -> if Double.IsNaN d || Double.IsInfinity d
then upcast JValue(d), Some("double")
else upcast JValue(d), None
| String(s) -> upcast JValue(s), if s = null then Some("string") else None
| Bool(b) -> upcast JValue(b), None
| DateTime(dt) -> upcast JValue(dt), Some("datetime")
| Guid(g) -> upcast JValue(g.ToString()), Some("guid")
// Arrays of primitives
| IntArray(a) -> upcast JValue(a |> Array.ofSeq |> Utils.encode1DArray), Some("int array")
| UIntArray(a) -> upcast JValue(a |> Array.ofSeq |> Utils.encode1DArray), Some("uint array")
| Int64Array(a) -> upcast JValue(a |> Array.ofSeq |> Utils.encode1DArray), Some("int64 array")
| UInt64Array(a) -> upcast JValue(a |> Array.ofSeq |> Utils.encode1DArray), Some("uint64 array")
| DoubleArray(a) -> upcast JValue(a |> Array.ofSeq |> Utils.encode1DArray), Some("double array")
| DecimalArray(a) -> upcast JValue(a |> Array.ofSeq |> Utils.encodeDecimalArray), Some("decimal array")
| BoolArray(a) -> upcast JValue(a |> Array.ofSeq |> Utils.encode1DArray), Some("bool array")
| DateTimeArray(a) -> upcast JValue(a |> Seq.map Utils.DateTimeToUnixEpoch |> Array.ofSeq |> Utils.encode1DArray), Some("datetime array")
| StringArray(a) -> upcast JArray(a |> Array.ofSeq), if Seq.isEmpty a then Some("string array") else None
| ByteArray(a) -> upcast JValue(a |> Array.ofSeq |> Utils.encode1DArray), Some("byte array")
// Mapping
| Artefact(typeID, content) -> Json.Marshal(content, writer), Some(typeID)
| Map(map) -> encodeMap(map), None
// Sequence
| Seq(s) -> let items = s |> Seq.map (fun i -> Json.Marshal(i, writer))
upcast JArray(items), None
// Blobs and namespaces
| Namespace(names, infoSet) -> let result = JObject()
result.Add("name", JArray(names))
let nsc = getJson(infoSet, match writer with
| None -> None
| Some(origin) -> let mutable w = origin
for n in names do
w <- w.AddGroup(n)
Some(w))
result.Add(Utils.encodeNameAndType("content", snd nsc), fst nsc)
upcast result, Some("namespace")
| Blob(name,blob) -> let result = JObject()
result.Add("name", JValue(name))
match writer with
| Some(w) -> w.Write(name, blob)
| None -> use reader = new System.IO.BinaryReader(blob.GetStream())
result.Add("data", JValue(System.Convert.ToBase64String(reader.ReadBytes(int(reader.BaseStream.Length)))))
upcast result, Some("data")
match getJson(infoSet, writer) with
| token, None -> token
| token, Some(typeID) -> let res = JObject()
res.Add(Utils.encodeNameAndType("", Some(typeID)), token)
res :> JToken
static member Unmarshal(token, reader) =
let rec getInfoSet (token : JToken, t : string option, reader : IBlobReader option) : InfoSet =
let decodeMap (obj : JObject) =
let mutable nts : (string * string option * InfoSet) list = List.empty
for pair in obj do
let n,t = Utils.decodeNameAndType pair.Key
nts <- (n,t,getInfoSet(pair.Value, t, reader)) :: nts
if nts.Length = 1
then
match nts.Head with
| "", _, infoSet -> infoSet
| n, _, infoSet -> InfoSet.EmptyMap.AddInfoSet(n,infoSet)
else
nts |> List.map (fun t -> let n,_,s = t in n,s) |> InfoSet.ofPairs
match t with
| None -> match token.Type with
| JTokenType.Null -> InfoSet.Null
| JTokenType.Integer -> InfoSet.Int(token.Value<int>())
| JTokenType.Float -> InfoSet.Double(token.Value<float>())
| JTokenType.String -> InfoSet.String(token.Value<string>())
| JTokenType.Boolean -> InfoSet.Bool(token.Value<bool>())
| JTokenType.Array ->
let arr = token :?> JArray
if arr.Count > 0 && arr |> Seq.forall (fun i -> i.Type = JTokenType.String)
then InfoSet.StringArray(arr |> Seq.map (fun i -> i.Value<string>()))
else InfoSet.Seq(arr |> Seq.map (fun i -> Json.Unmarshal(i, reader)) |> List.ofSeq)
| JTokenType.Object -> token :?> JObject |> decodeMap
| _ -> failwith ("Cannot create InfoSet from JToken of type " + token.Type.ToString())
// Primitives
| Some("int") -> InfoSet.Int(token.Value<int>())
| Some("uint") -> InfoSet.UInt(token.Value<UInt32>())
| Some("int64") -> InfoSet.Int64(token.Value<Int64>())
| Some("uint64") -> InfoSet.UInt64(if token.Type = JTokenType.String then BitConverter.ToUInt64(Convert.FromBase64String(token.Value<string>()), 0)
else token.Value<UInt64>())
| Some("decimal") -> InfoSet.Decimal(if token.Type = JTokenType.String then let bytes = Convert.FromBase64String(token.Value<string>())
let ints = [|0..3|] |> Array.map (fun i -> BitConverter.ToInt32(bytes, i * 4))
Decimal(ints)
else token.Value<decimal>())
| Some("guid") -> InfoSet.Guid(Guid.Parse(token.Value<string>()))
| Some("datetime") -> InfoSet.DateTime(token.Value<DateTime>())
| Some("double") -> InfoSet.Double(token.Value<double>())
| Some("string") -> InfoSet.String(token.Value<string>())
// Arrays
| Some("string array") -> InfoSet.StringArray(token.Values<string>())
| Some("int array") -> InfoSet.IntArray(Utils.decode1DArray(token.Value<string>(), 4))
| Some("uint array") -> InfoSet.UIntArray(Utils.decode1DArray(token.Value<string>(), 4))
| Some("int64 array") -> InfoSet.Int64Array(Utils.decode1DArray(token.Value<string>(), 8))
| Some("uint64 array") -> InfoSet.UInt64Array(Utils.decode1DArray(token.Value<string>(), 8))
| Some("double array") -> InfoSet.DoubleArray(Utils.decode1DArray(token.Value<string>(), 8))
| Some("decimal array") -> InfoSet.DecimalArray(Utils.decodeDecimalArray(token.Value<string>()))
| Some("datetime array") -> InfoSet.DateTimeArray(Utils.decode1DArray<float>(token.Value<string>(), 8) |> Array.map Utils.UnixEpochToDateTime)
| Some("bool array") -> InfoSet.BoolArray(Utils.decode1DArray(token.Value<string>(), 1))
| Some("byte array") -> InfoSet.ByteArray(Utils.decode1DArray(token.Value<string>(), 1))
// Blobs
| Some("data") -> let obj = token :?> JObject
let name = obj.["name"].Value<string>()
InfoSet.Blob(name, match obj.TryGetValue("data") with
| true, data -> InlineBase64Blob(data.Value<string>()) :> IBlob
| false, _ -> match reader with
| Some(r) -> r.Read(name)
| None -> failwith ("Cannot read blob " + name + ". Reader is not supplied"))
| Some("namespace") -> let obj = token :?> JObject
let mutable names = List.empty<string>
let mutable token : JToken = null
let mutable typeID : string option = None
for pair in obj do
match Utils.decodeNameAndType pair.Key with
| "content", t -> token <- pair.Value
typeID <- t
| "name", None -> names <- List.ofSeq(pair.Value.Values<string>())
| n,_ -> failwith ("Cannot decode namespace record. Unknown field " + n)
InfoSet.Namespace(names, getInfoSet(token, typeID, match reader with
| None -> None
| Some(origin) ->
let mutable r = origin
for n in names do
r <- r.GetGroup(n)
Some(r)))
// Generic type
| Some(t) -> Artefact(t, match token with
| :? JObject as obj -> decodeMap obj
| :? JArray as arr -> InfoSet.Seq(arr |> Seq.map (fun i -> getInfoSet(i, None, reader)))
| token -> Unmarshal(token, reader))
InfoSet.Namespace(names, getInfoSet(token, typeID, match reader with
| None -> None
| Some(origin) ->
let mutable r = origin
for n in names do
r <- r.GetGroup(n)
Some(r)))
// Generic type
| Some(t) -> Artefact(t, match token with
| :? JObject as obj -> decodeMap obj
| :? JArray as arr -> InfoSet.Seq(arr |> Seq.map (fun i -> getInfoSet(i, None, reader)))
| token -> Json.Unmarshal(token, reader))
let infoSet = getInfoSet(token, None, reader)
match infoSet with
| Map(map) when map.Count = 1 -> let pair = (map |> Map.toArray).[0]
match fst pair |> decodeNameAndType with
| "", Some(typeID) -> InfoSet.Artefact(typeID, snd pair)
| _ -> infoSet
| infoSet -> infoSet
let infoSet = getInfoSet(token, None, reader)
match infoSet with
| Map(map) when map.Count = 1 -> let pair = (map |> Map.toArray).[0]
match fst pair |> Utils.decodeNameAndType with
| "", Some(typeID) -> InfoSet.Artefact(typeID, snd pair)
| _ -> infoSet
| infoSet -> infoSet
static member FromObject (resolver, a) = Json.Marshal(ArtefactSerializer.Serialize resolver a, None)
static member FromObject (resolver, a, writer) = Json.Marshal(ArtefactSerializer.Serialize resolver a, Some(writer))
static member ToObject<'t> (json, resolver) = ArtefactSerializer.Deserialize resolver (Json.Unmarshal(json, None))
static member ToObject<'t> (json, resolver, reader) = ArtefactSerializer.Deserialize resolver (Json.Unmarshal(json, Some(reader)))

Просмотреть файл

@ -6,7 +6,7 @@ open System.Runtime.InteropServices
module internal Const =
[<Literal>]
let Version = "0.1.1" // Assembly semantic version
let Version = "0.1.2" // Assembly semantic version
[<assembly: AssemblyTitle("Angara.Serialization")>]
[<assembly: AssemblyDescription("A framework to serialize and deserialize objects into and from a special InfoSet type, which facilitates further persistence or transport")>]

Просмотреть файл

@ -80,6 +80,8 @@
this.v = v;
}
public get Type() { return this.t; }
public static get EmptyMap() { return new InfoSet(InfoSetType.Map, {}); }
public AddInfoSet(p: string, i: InfoSet) {
@ -288,16 +290,7 @@
return [Base64.Encode(typeof i.v == "Float64Array" ? i.v : new Float64Array(arr)), "datetime array"];
} else if (i.IsSeq) {
var seq = <InfoSet[]>i.v;
if (seq.length > 0) {
var res = {};
for (var idx = 0; idx < seq.length; idx++) {
var encoded = InfoSet.Encode(seq[idx]);
var key = InfoSet.EncodeNameAndType(idx.toString(), encoded[1]);
res[key] = encoded[0];
}
return [res, "array"];
} else
return [[], null];
return [seq.map(i => InfoSet.Marshal(i)),null]
} else if (i.IsMap) {
var res = {};
for (var p in i.v) {
@ -379,14 +372,10 @@
} else if (typeId == "double array")
return InfoSet.DoubleArray(new Float64Array(Base64.Decode(json).buffer));
else if (typeId == "array") {
var a = new Array<InfoSet>();
for (var p in json) {
var nameType = InfoSet.DecodeNameAndType(p);
var index = parseInt(nameType[0]);
if (index == NaN)
throw new Error("Error decoding sequence. Cannot parse " + nameType[0] + " as integer");
a[index] = InfoSet.Decode([json[p], nameType[1]]);
}
var length = json.length;
var a = new Array<InfoSet>(length);
for (var i = 0;i<length;i++)
a[i] = InfoSet.Unmarshal(json[i]);
return InfoSet.Seq(a);
} else if (typeId != null) {
var typeEnd = typeId.split(" ");

Просмотреть файл

@ -24,22 +24,7 @@ describe("Angara.InfoSet", () => {
expect(JSON.stringify(c_json)).toEqual(JSON.stringify(s_json));
});
it("generates server-compatible JSON for arrays", () => {
var c_json = a.InfoSet.Marshal(a.InfoSet.EmptyMap
.AddIntArray("ia", [20, 30])
.AddBoolArray("ba", [true, false])
.AddDoubleArray("da", [1e-12, 1e+20, 3.1415, 2.87])
.AddDateTimeArray("datea", [new Date("2015-10-08"), new Date("2014-08-15")])
.AddStringArray("sa", ["hello", "world", "!"]));
var s_json = {
"ia:int array": "FAAAAB4AAAA=",
"ba:bool array": "AQA=",
"da:double array": "EeotgZmXcT1AjLV4Ha8VRG8Sg8DKIQlA9ihcj8L1BkA=",
"datea:datetime array": "AAAA9UsEdUIAAMBrb310Qg==",//???
"sa": ["hello", "world", "!"]
};
expect(JSON.stringify(c_json)).toEqual(JSON.stringify(s_json));
});
it("restores server-compatible JSON for arrays", () => {
var da = [1e-12, 1e+20, 3.1415, 2.87];
@ -86,15 +71,7 @@ describe("Angara.InfoSet", () => {
var ia = seq[1].ToIntArray();
expect(ia.length).toBe(51);
})
it("generates server-compatible JSON for seq ", () => {
var da = [a.InfoSet.Int(10), a.InfoSet.String("hello"), a.InfoSet.DoubleArray([10.3, 5])];
var c_json = a.InfoSet.Marshal(a.InfoSet.EmptyMap
.AddSeq("s", da));
var s_json = { "s:array": { "0:int":10, "1":"hello", "2:double array":"mpmZmZmZJEAAAAAAAAAUQA=="}};
expect(JSON.stringify(c_json)).toEqual(JSON.stringify(s_json));
});
it("roundtrips integer values", () => {
var infoSet = a.InfoSet.Unmarshal(a.InfoSet.Marshal(a.InfoSet.Int(10)));
expect(infoSet.IsInt).toBeTruthy();
@ -217,7 +194,8 @@ describe("Angara.InfoSet", () => {
it("roundtrips Seq values", () => {
var da = [a.InfoSet.String("hello"), a.InfoSet.BoolArray([true, false]), a.InfoSet.Int(10)];
var infoSet = a.InfoSet.Unmarshal(a.InfoSet.Marshal(a.InfoSet.Seq(da)));
var json = a.InfoSet.Marshal(a.InfoSet.Seq(da));
var infoSet = a.InfoSet.Unmarshal(json);
expect(infoSet.IsSeq).toBeTruthy();
var arr = infoSet.ToSeq();
expect(arr[0]).toEqual(da[0]);

Просмотреть файл

@ -0,0 +1,57 @@
/// <reference path="../../../typings/jasmine/jasmine.d.ts"/>
/// <reference path="../../../dist/Angara.Serialization.d.ts"/>
describe("Angara.InfoSet", () => {
it("parses and deserializes object arrays",() => {
var json = {
":object array": [
null,
3.1415926535897931,
{
":record": {
"Age:int": 25,
"Dogs": [
"Alba",
"Eva" ],
"Name": "Adam"
}
}]
}
var infoSet = Angara.InfoSet.Unmarshal(json);
var a = infoSet.ToArtefact();
expect(a.TypeId).toBe("object array");
var c = a.Content;
var a2 = c.ToSeq();
expect(a2[0].IsNull).toBeTruthy();
expect(a2[1].ToDouble()).toBe(3.1415926535897931);
expect(a2[2].ToArtefact().TypeId).toBe("record");
});
it("generates server-compatible JSON for arrays", () => {
var c_json = Angara.InfoSet.Marshal(Angara.InfoSet.EmptyMap
.AddIntArray("ia", [20, 30])
.AddBoolArray("ba", [true, false])
.AddDoubleArray("da", [1e-12, 1e+20, 3.1415, 2.87])
.AddDateTimeArray("datea", [new Date("2015-10-08"), new Date("2014-08-15")])
.AddStringArray("sa", ["hello", "world", "!"]));
var s_json = {
"ia:int array": "FAAAAB4AAAA=",
"ba:bool array": "AQA=",
"da:double array": "EeotgZmXcT1AjLV4Ha8VRG8Sg8DKIQlA9ihcj8L1BkA=",
"datea:datetime array": "AAAA9UsEdUIAAMBrb310Qg==",//???
"sa": ["hello", "world", "!"]
};
expect(JSON.stringify(c_json)).toEqual(JSON.stringify(s_json));
});
it("generates server-compatible JSON for seq ", () => {
var da = Angara.InfoSet.Seq([Angara.InfoSet.Int(10), Angara.InfoSet.String("hello"), Angara.InfoSet.DoubleArray([10.3, 5])]);
var c_json = Angara.InfoSet.Marshal(da);
var s_json = [ { ":int":10 } ,
"hello",
{ ":double array":"mpmZmZmZJEAAAAAAAAAUQA==" } ];
expect(JSON.stringify(c_json)).toEqual(JSON.stringify(s_json));
});
});

Просмотреть файл

@ -5,7 +5,7 @@
"path": "typings",
"installed": {
"jasmine/jasmine.d.ts": {
"commit": "1a5b2a729d35b1d41e2d15818db013a3644c6ba4"
"commit": "aefd8146158b9f17c9eb79bbf158e6d8d3c2a87b"
}
}
}