Родитель
b7bfcb4682
Коммит
3f1257209b
|
@ -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));
|
||||
});
|
||||
|
||||
});
|
2
tsd.json
2
tsd.json
|
@ -5,7 +5,7 @@
|
|||
"path": "typings",
|
||||
"installed": {
|
||||
"jasmine/jasmine.d.ts": {
|
||||
"commit": "1a5b2a729d35b1d41e2d15818db013a3644c6ba4"
|
||||
"commit": "aefd8146158b9f17c9eb79bbf158e6d8d3c2a87b"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче