Skip to content

Instantly share code, notes, and snippets.

@robkuz
Last active October 11, 2018 15:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robkuz/d3a1c6f6982d988f6416b99a34002b4b to your computer and use it in GitHub Desktop.
Save robkuz/d3a1c6f6982d988f6416b99a34002b4b to your computer and use it in GitHub Desktop.
Json Testing Lib in F#

Small testing library for Newtonsoft.Json

I need a way to compare the output of a JSON/REST API. Using string was to cumbersome. This library consists of 2 functions

API

compareObjects: JObject -> JObject -> string -> List<string>
compareArrays: JArray -> JArray -> string -> List<string>

which either compare JObjects or JArrays and recurse into the Json structure if needed. It will return all the differences between the source object/array and the target object/array

Results

Example 1

  • Show different property values on structurally similar objects
  • Show different property types on structurally similar objects
  • Show missing properties in either the source or the target object
    [
        "#.Drives.DVD.(source.value: false) != #.Drives.DVD.(target.value: true)";
        "#.Drives.Gigabytes not found in target";
        "#.Drives.Gigabytes1 not found in source";
        "#.Ram.(source.type: Integer) != #.Ram.(target.type: String)"
    ]

Example 2

  • Show different array sizes
    [
        "#.Ports.Count.(source: 2) != #.Ports.Count.(target: 3)"
    ]

Example 3

  • Show different types for array elements
    [
         "#.Ports[2].foo[1].(source.type: Integer) != #.Ports[2].foo[1].(target.type: String)"
    ]

FAQ

  • Is the order of properties significant for JObjects? Nope. That was the reason I was writing this in the first place so I could go away from string comparison

  • Is the order of elements in the array significant? Yes. From my perspective there is no good strategy to compare an array and not use its index.

  • Will you create a Nuget package for this? Nope!

  • Why? Because I am too lazy and I don't want to maintain a repo. Apart from that it's really 110 LOC. So just copy this stuff in your project and be done

open System
open Newtonsoft.Json.Linq
module JsonTest =
let tokenType2String tt =
match tt with
| JTokenType.None -> "None"
| JTokenType.Object -> "Object"
| JTokenType.Array -> "Array"
| JTokenType.Constructor -> "Constructor"
| JTokenType.Property -> "Property"
| JTokenType.Comment -> "Comment"
| JTokenType.Integer -> "Integer"
| JTokenType.Float -> "Float"
| JTokenType.String -> "String"
| JTokenType.Boolean -> "Boolean"
| JTokenType.Null -> "Null"
| JTokenType.Undefined -> "Undefined"
| JTokenType.Date -> "Date"
| JTokenType.Raw -> "Raw"
| JTokenType.Bytes -> "Bytes"
| JTokenType.Guid -> "Guid"
| JTokenType.Uri -> "Uri"
| JTokenType.TimeSpan -> "TimeSpan"
| _ -> failwith "Should never happen!"
let toObject (t: JToken) =
match t.Type with
| JTokenType.Integer -> t.ToObject<int>() |> box
| JTokenType.Float -> t.ToObject<float>() |> box
| JTokenType.String -> t.ToObject<string>() |> box
| JTokenType.Boolean -> t.ToObject<Boolean>() |> box
| _ -> failwith "Should never happen!!!!"
let getTypeName (target: JToken) = tokenType2String <| target.Type
let hasSameValues (target: JToken) (source: JToken) =
JToken.DeepEquals(target, source)
let hasSameTypes (target: JToken) (source: JToken) =
target.Type = source.Type
let rec compareObjects (s:JObject) (t:JObject) initkey =
let ss:seq<JProperty> = s |> Seq.cast
let ss' = ss |> List.ofSeq
let ts:seq<JProperty> = t |> Seq.cast
let ts' = ts |> List.ofSeq
let sks = ss' |> List.map (fun x -> x.Name)
let tks = ts' |> List.map (fun x -> x.Name)
let allkeys = sks @ tks |> List.distinctBy id
let check key =
let sv = s.GetValue(key)
let tv = t.GetValue(key)
if tv = null then
[(sprintf "%s.%s not found in %s" initkey key "target")]
elif sv = null then
[(sprintf "%s.%s not found in %s" initkey key "source")]
elif hasSameTypes sv tv |> not then
[(sprintf "%s.%s.(source.type: %s) != %s.%s.(target.type: %s)" initkey key (getTypeName sv) initkey key (getTypeName tv))]
elif sv.Type = JTokenType.Object then
compareObjects
(sv.ToObject<JObject>())
(tv.ToObject<JObject>())
(initkey + "." + key)
elif sv.Type = JTokenType.Array then
compareArrays
(sv.ToObject<JArray>())
(tv.ToObject<JArray>())
(initkey + "." + key)
elif hasSameValues sv tv |> not then
[sprintf "%s.%s.(source.value: %A) != %s.%s.(target.value: %A)" initkey key (toObject sv) initkey key (toObject tv)]
else
[]
allkeys
|> List.map check
|> List.concat
and compareArrays(s: JArray) (t: JArray) initkey =
let drillInto i =
let si = s.Item(i)
let ti = t.Item(i)
if hasSameTypes si ti |> not then
[(sprintf "%s[%i].(source.type: %s) != %s[%i].(target.type: %s)" initkey i (getTypeName si) initkey i (getTypeName ti))]
elif si.Type = JTokenType.Object then
compareObjects
(si.ToObject<JObject>())
(ti.ToObject<JObject>())
(initkey + "[" + i.ToString() + "]")
elif si.Type = JTokenType.Array then
compareArrays
(si.ToObject<JArray>())
(ti.ToObject<JArray>())
(initkey + "[" + i.ToString() + "]")
elif hasSameValues si ti |> not then
[sprintf "%s[%i].(source.value: %A) != %s[%i].(target.value: %A)" initkey i (toObject si) initkey i (toObject ti)]
else
[]
if s.Count <> t.Count then
[sprintf "%s.Count.(source: %A) != %s.Count.(target: %A)" initkey (s.Count) initkey (t.Count)]
else
seq {for i in 0 .. (s.Count - 1) do yield i}
|> Seq.toList
|> List.map drillInto
|> List.concat
open JsonTest
[<EntryPoint>]
let main argv =
let o1 = JObject.Parse(
"""{
'CPU': 'Intel',
'Drives': {
'DVD': false,
'Gigabytes': 500
},
'Ram': 400
}""")
let o2 = JObject.Parse(
"""{
'CPU': 'Intel',
'Ram': "400",
'Drives': {
'DVD': true,
'Gigabytes1': 501
}
}""")
let o3 = JObject.Parse(
"""{
'CPU': 'Intel',
'Drives': {
'DVD': true,
'Gigabytes': 500
},
'Ram': 400,
'Ports' : [
1,
2
]
}""")
let o4 = JObject.Parse(
"""{
'CPU': 'Intel',
'Ram': 400,
'Drives': {
'DVD': true,
'Gigabytes': 500
},
'Ports' : [
1,
2,
3
]
}""")
let o5 = JObject.Parse(
"""{
'CPU': 'Intel',
'Drives': {
'DVD': true,
'Gigabytes': 500
},
'Ram': 400,
'Ports' : [
1,
2,
{'foo': [1, 2]}
]
}""")
let o6 = JObject.Parse(
"""{
'CPU': 'Intel',
'Ram': 400,
'Drives': {
'DVD': true,
'Gigabytes': 500
},
'Ports' : [
1,
2,
{'foo': [1, 'bar']}
]
}""")
printfn "RESULT: %A" (compareObjects o1 o2 "#")
printfn "RESULT: %A" (compareObjects o3 o4 "#")
printfn "RESULT: %A" (compareObjects o5 o6 "#")
0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment