6

I'm trying to add the serialized data in a request to third party API which needs a specific order of the data to be maintained, but SerializeJSON orders in alphabetical order which breaks the format required by the third party API. Could someone help me to figure it out

INPUT:

<cfset data ={
                "Booking": {
                    "ActionCode":"DI",
                    "AgencyNumber":"23",
                    "Touroperator":"TVR",
                    "BookingNumber":"323",
                },
                "Payment": {
                    "__type":"paymenttype",
                    "PaymentProfile": {
                        "Value": 4,
                        "Manual": false
                    },
                    "PaymentType": 4,
                    "PaymentAction":2,
                    "Details": {
                        "IBAN": "DE02120300000000202051",
                        "BIC": "BYLADEM1001"
                    }
                },
                "Login":{
                    "UserCode": "usercode",
                    "Password": "password"
                }
            }>

When this method SerializeJSON() is used on my data:

SerializeJSON(data)

Current Output

"{"Booking":{"Touroperator":"TVR","ActionCode":"DI","BookingNumber":"323","AgencyNumber":"23"},"Login":{"UserCode":"usercode","Password":"password"},"Payment":{"PaymentProfile":{"Manual":false,"Value":4},"PaymentType":4,"PaymentAction":2,"__type":"paymenttype","Details":{"BIC":"BYLADEM1001","IBAN":"DE02120300000000202051"}}}"

Expected Output:

"{"Booking":{"ActionCode":"DI","AgencyNumber":"23","Touroperator":"TVR","BookingNumber":"323",},"Payment":{"__type":"paymenttype","PaymentProfile":{"Value":4,"Manual":false},"PaymentType":4,"PaymentAction":2,"Details":{"IBAN":"DE02120300000000202051","BIC":"BYLADEM1001"}},"Login":{"UserCode":"usercode","Password":"password"}}"
ro ko
  • 2,906
  • 3
  • 37
  • 58

1 Answers1

8

Structs in ColdFusion are unordered HashMaps, so there is no order at all. You can keep insertion order by using structNew("Ordered") (introduced with ColdFusion 2016). Unfortunately you can no longer use the literal syntax anymore, but I assume you are generating the data dynamically anyway.

<cfset data = structNew("Ordered")>

<cfset data["Booking"] = structNew("Ordered")>
<cfset data["Booking"]["ActionCode"] = "DI">
<cfset data["Booking"]["AgencyNumber"] = "TVR">
<cfset data["Booking"]["BookingNumber"] = "323">

<cfset data["Payment"] = structNew("Ordered")>
<cfset data["Payment"]["__type"] = "paymenttype">
<cfset data["Payment"]["PaymentProfile"] = structNew("Ordered")>
<cfset data["Payment"]["PaymentProfile"]["Value"] = 4>
<cfset data["Payment"]["PaymentProfile"]["Manual"] = false>

etc.

If you are stuck on an older ColdFusion version, you will have to use Java's LinkedHashMap.

<cfset data = createObject("java", "java.util.LinkedHashMap")>

<cfset data["Booking"] = createObject("java", "java.util.LinkedHashMap")>
<cfset data["Booking"]["ActionCode"] = "DI">
<cfset data["Booking"]["AgencyNumber"] = "TVR">
<cfset data["Booking"]["BookingNumber"] = "323">

<cfset data["Payment"] = createObject("java", "java.util.LinkedHashMap")>
<cfset data["Payment"]["__type"] = "paymenttype">
<cfset data["Payment"]["PaymentProfile"] = createObject("java", "java.util.LinkedHashMap")>
<cfset data["Payment"]["PaymentProfile"]["Value"] = 4>
<cfset data["Payment"]["PaymentProfile"]["Manual"] = false>

etc.

But be aware: LinkedHashMap is case-sensitive (and also type-sensitive: in case your keys are numbers, it does matter!).

<cfset data = createObject("java", "java.util.LinkedHashMap")>

<cfset data["Test"] = "">
<!---
    accessing data["Test"] = works
    accessing data["test"] = doesn't work
    accessing data.Test    = doesn't work
--->

Another issue you might encounter: Due to ColdFusion's internal type casting, serializeJSON() might stringify numbers and booleans in an unintended way. Something like:

<cfset data = structNew("Ordered")>
<cfset data["myBoolean"] = true>
<cfset data["myInteger"] = 123>

could easily end up like:

{
    "myBoolean": "YES",
    "myInteger": 123.0
}

(Note: The above literal syntax would work perefectly fine, but if you are passing the values around as variables/arguments, casting eventually happens.)

The easiest workaround is explicitly casting the value before serializing:

<cfset data = structNew("Ordered")>
<cfset data["myBoolean"] = javaCast("boolean", true)>
<cfset data["myInteger"] = javaCast("int", 123)>
Alex
  • 7,743
  • 1
  • 18
  • 38
  • Didn't know there was support for ordered structures now. Good tip. Though disappointing Adobe didn't extend support to literal syntax. – SOS Apr 22 '19 at 07:49
  • Reposting this tip as the previous comment disappeared for some reason: The shortcut for an ordered structure is `[:]`. – SOS Apr 22 '19 at 07:50