DW差异功能

时间:2020-04-29 16:05:02

标签: dataweave

我正在使用DW diff函数返回由编辑配置文件引起的差异,但是在嵌套对象的情况下,该函数返回的内容似乎存在问题。我需要同时返回字段的旧值和该字段的新值。这是一个示例:

输入

[
  {
    "firstName": "Gary",
    "lastName": "Nelson",
    "email": "sampleaccount23@testdata112.com",
    "address": {
      "street": "10 S. Main St",
      "city": "Atlanta",
      "state": "GA",
      "country": "US",
      "postalCode": "10209"
    },
    "timestamp": "2010-04-17T15:09:40.51"
  },
  {
    "firstName": "Gary",
    "lastName": "Nelson",
    "email": "sampleaccount23@testdata112.com",
    "address": {
      "street": "7 S. Main St",
      "city": "Atlanta",
      "state": "GA",
      "country": "US",
      "postalCode": "10209"
    },
    "timestamp": "2010-04-17T15:09:30.51"
  }
]

DW变换

%dw 2.0
import diff from dw::util::Diff
output application/json
---
diff(payload[0], payload[1])

输出

{
  "matches": false,
  "diffs": [
    {
      "expected": "\"2010-04-17T15:09:40.51\"",
      "actual": "\"2010-04-17T15:09:30.51\"",
      "path": "(root).timestamp"
    },
    {
      "expected": "\"7 S. Main St\"",
      "actual": "\"10 S. Main St\"",
      "path": "(root).address.street"
    }
  ]
}

时间戳字段上的差异显示为预期值,但输出中的第二个差异结果与预期的结果相反:10 S. Broadway St应为expected值(即新的更新的配置文件值)和7 S. Broadway St actual(即旧的配置文件值)。第一个输入对象(payload[0])中的所有值应为expected值,第二个输入对象(payload[1])中的所有值应为actual。据我所知,只有在要比较的项目中有嵌套对象时,这种情况才会发生。

1 个答案:

答案 0 :(得分:3)

Stevens似乎您发现了一个错误:(。。对您来说,diff函数完全在数据编织中实现,因此在这里我将使用修复程序粘贴Diff模块。您可以将其粘贴到本地项目中的 src / main / resource / com / mycompany / Diff.dwl ,然后将其导入为 com :: mycompany :: Diff

/**
* This utility module calculates the difference between two values
* and returns the list of differences.
*
* The module is included with Mule runtime. To use it, you must import it to your
* DataWeave code, for example, by adding the line `import dw::util::Diff` or
* `import * from dw::util::Diff` to your header.
*/
%dw 2.0

import entrySet, keySet from dw::core::Objects

// A type: `Diff`.
/**
* Describes the entire difference between two values.
*
* *Example with no differences*:
*
* `{ "matches": true, "diffs": [ ] }`
*
* *Example with differences*:
*
* `{ "matches": true, "diffs": [ "expected": "4", "actual": "2", "path": "(root).a.@.d" ] }`
*
* See the `diff` function for another example.
*/
type Diff = {
    "matches": Boolean,
    diffs: Array<Difference>
}

// A type: Difference.
/**
* Describes a single difference between two values at a given structure.
*/
type Difference = {
    expected: String,
    actual: String,
    path: String
}

/**
* Returns the structural differences between two values.
*
*
* Differences between objects can be ordered (the default) or unordered. Ordered
* means that two objects do not differ if their key-value pairs are in the same
* order. Differences are expressed as `Difference` type.
*
* === Parameters
*
* [%header, cols="1,3"]
* |===
* | Name | Description
* | `actual` | The actual value. Can be any data type.
* | `expected` | The expected value to compare to the actual. Can be any data type.
* | `diffConfig` | Setting for changing the default to unordered using `{ "unordered" : true} (explained in the introduction).
* |===
*
* === Example
*
* This example shows a variety of uses of `diff`.
*
* ==== Source
*
* [source,Dataweave, linenums]
* ----
* import diff from dw::util::Diff
* ns ns0 http://locahost.com
* ns ns1 http://acme.com
* output application/dw
* ---
* {
*   "a": diff({a: 1}, {b:1}),
*   "b": diff({ns0#a: 1}, {ns1#a:1}),
*   "c": diff([1,2,3], []),
*   "d": diff([], [1,2,3]),
*   "e": diff([1,2,3], [1,2,3, 4]),
*   "f": diff([{a: 1}], [{a: 2}]),
*   "g": diff({a @(c: 2): 1}, {a @(c: 3): 1}),
*   "h": diff(true, false),
*   "i": diff(1, 2),
*   "j": diff("test", "other test"),
*   "k": diff({a: 1}, {a:1}),
*   "l": diff({ns0#a: 1}, {ns0#a:1}),
*   "m": diff([1,2,3], [1,2,3]),
*   "n": diff([], []),
*   "o": diff([{a: 1}], [{a: 1}]),
*   "p": diff({a @(c: 2): 1}, {a @(c:2): 1}),
*   "q": diff(true, true),
*   "r": diff(1, 1),
*   "s": diff("other test", "other test"),
*   "t": diff({a:1 ,b: 2},{b: 2, a:1}, {unordered: true}),
*   "u": [{format: "ssn",data: "ABC"}] diff [{ format: "ssn",data: "ABC"}]
* }
* ----
*
* ==== Output
*
* [source,XML,linenums]
* ----
* ns ns0 http://locahost.com
* ns ns1 http://acme.com
* ---
* {
*   a: {
*     matches: false,
*     diffs: [
*       {
*         expected: "Entry (root).a with type Number",
*         actual: "was not present in object.",
*         path: "(root).a"
*       }
*     ]
*   },
*   b: {
*     matches: false,
*     diffs: [
*       {
*         expected: "Entry (root).ns0#a with type Number",
*         actual: "was not present in object.",
*         path: "(root).ns0#a"
*       }
*     ]
*   },
*   c: {
*     matches: false,
*     diffs: [
*       {
*         expected: "Array size is 0",
*         actual: "was 3",
*         path: "(root)"
*       }
*     ]
*   },
*   d: {
*     matches: false,
*     diffs: [
*       {
*         expected: "Array size is 3",
*         actual: "was 0",
*         path: "(root)"
*       }
*     ]
*   },
*   e: {
*     matches: false,
*     diffs: [
*       {
*         expected: "Array size is 4",
*         actual: "was 3",
*         path: "(root)"
*       }
*     ]
*   },
*   f: {
*     matches: false,
*     diffs: [
*       {
*         expected: "1" as String {mimeType: "application/dw"},
*         actual: "2" as String {mimeType: "application/dw"},
*         path: "(root)[0].a"
*       }
*     ]
*   },
*   g: {
*     matches: false,
*     diffs: [
*       {
*         expected: "3" as String {mimeType: "application/dw"},
*         actual: "2" as String {mimeType: "application/dw"},
*         path: "(root).a.@.c"
*       }
*     ]
*   },
*   h: {
*     matches: false,
*     diffs: [
*       {
*         expected: "false",
*         actual: "true",
*         path: "(root)"
*       }
*     ]
*   },
*   i: {
*     matches: false,
*     diffs: [
*       {
*         expected: "2",
*         actual: "1",
*         path: "(root)"
*       }
*     ]
*   },
*   j: {
*     matches: false,
*     diffs: [
*       {
*         expected: "\"other test\"",
*         actual: "\"test\"",
*         path: "(root)"
*       }
*     ]
*   },
*   k: {
*     matches: true,
*     diffs: []
*   },
*   l: {
*     matches: true,
*     diffs: []
*   },
*   m: {
*     matches: true,
*     diffs: []
*   },
*   n: {
*     matches: true,
*     diffs: []
*   },
*   o: {
*     matches: true,
*     diffs: []
*   },
*   p: {
*     matches: true,
*     diffs: []
*   },
*   q: {
*     matches: true,
*     diffs: []
*   },
*   r: {
*     matches: true,
*     diffs: []
*   },
*   s: {
*     matches: true,
*     diffs: []
*   },
*   t: {
*     matches: true,
*     diffs: []
*   },
*   u: {
*     matches: true,
*     diffs: []
*   }
* }
* ----
*/
fun diff(actual: Any, expected:Any, diffConfig: {unordered?: Boolean} = {} , path:String = "(root)"): Diff  = do {

    fun createDiff(expected:String, actual:String, path: String): Diff =
        {
            matches: false,
            diffs: [{ expected: expected, actual: actual, path: path }]
        }
    fun createMatch(): Diff =
        {
            matches: true,
            diffs: []
        }

    fun mergeDiff(left:Diff, right:Diff): Diff =
        {
            matches: left.matches and right.matches,
            diffs: left.diffs ++ right.diffs,
        }

    fun keyToString(k:Key):String = do {
        var namespace = if(k.#?) (k.# as Object) else {}
        ---
        if(namespace.prefix?)
            "$(namespace.prefix!)#$(k)"
        else
            k as String
    }

    fun entries(obj: Object): Array<Array<Any>> = obj pluck [$$,$]

    fun namesAreEquals(akey: Key, ekey: Key): Boolean =
        (akey as String == ekey as String) and ((akey.#.uri == ekey.#.uri) or (isEmpty(akey.#.uri) and isEmpty(ekey.#.uri)))

    fun isEmptyAttribute(attrs: Object | Null) =
        attrs == null or attrs == {}

    fun diffAttributes(actual: Any, expected:Any, path:String): Diff = do {
        var actualAttributes = actual.@
        var expectedAttributes = expected.@
        ---
        if(isEmptyAttribute(actualAttributes) and not isEmptyAttribute(expectedAttributes))
            createDiff("Attributes $(write(expectedAttributes))", "Empty attributes.", path)
        else if((not isEmptyAttribute(actualAttributes)) and isEmptyAttribute(expectedAttributes))
            createDiff("Empty attributes", "Attributes $(write(expectedAttributes))", path)
        else if(isEmptyAttribute(expectedAttributes) and isEmptyAttribute(actualAttributes))
            createMatch()
        else
            diff(actualAttributes, expectedAttributes, diffConfig, path)
    }

    fun diffObjects(actual: Object, expected: Object, path: String = "(root)"): Diff = do {
        var aobject = if(diffConfig.unordered default false) actual orderBy $$ else actual
        var eobject = if(diffConfig.unordered default false) expected orderBy $$ else expected
        ---
        if(sizeOf(aobject) == sizeOf(eobject)) do {
            var matchResult = {diff: createMatch(), remaining: aobject }
            ---
            zip(entries(aobject), entries(eobject)) map ((actualExpected) -> do {
                var actualEntry = actualExpected[0]
                var expectedEntry = actualExpected[1]
                var expectedKey = expectedEntry[0]
                var expectedKeyString = keyToString(expectedKey)
                var attributeDiff = diffAttributes(actualEntry[0], expectedKey, "$(path).$(expectedKeyString).@")
                var valueDiff = diff(actualEntry[1], expectedEntry[1], diffConfig, "$(path).$(expectedKeyString)")
                ---
                if(namesAreEquals(actualEntry[0], expectedKey))
                    mergeDiff(attributeDiff, valueDiff)
                else do {
                    var expectedValueType = typeOf(expectedEntry[1])
                    ---
                    createDiff("Entry $(path).$(expectedKeyString) with type $(expectedValueType)", "was not present in object.", "$(path).$(expectedKeyString)")
                }

            })
            reduce ((value, acc = createMatch()) -> mergeDiff(value, acc))
        }
        else
            createDiff("Object size is $(sizeOf(eobject))", "$(sizeOf(aobject))", path)
    }

    ---
    expected match {
        case eobject is Object -> do {
            actual match {
                 case aobject is Object ->
                    diffObjects(aobject, eobject, path)
                 else ->
                    createDiff("Object type", "$(typeOf(actual)) type" , path)
             }
        }
        case earray is Array -> do {
             actual match {
                 case aarray is Array ->
                    if(sizeOf(aarray) == sizeOf(earray))
                      zip(aarray, earray)
                        map ((actualExpected, index) ->
                            diff(actualExpected[0], actualExpected[1], diffConfig, "$(path)[$(index)]")
                        )
                        reduce ((value, acc = createMatch()) ->
                            mergeDiff(value, acc)
                        )
                    else
                      createDiff("Array size is $(sizeOf(earray))", "was $(sizeOf(aarray))" , path)
                 else ->
                    createDiff("Array type", "$(typeOf(actual)) type", path)
             }
        }
        else ->
            if(actual == expected)
              createMatch()
            else
              createDiff("$(write(expected))", "$(write(actual))", path)
    }
}