5

I am trying to pass a struct through a function, but the integers in it are converting to scientific notation.

Before deSerialization :

{"businessUnitValidList":2003051509034372557922
  , "shortMessage":"Success"
  , "longMessage":"Request Completed Successfully."
  , "status":20001
} 

After deSerialization:

 businessUnitValidList  2.00305150903E+021 

I have tried converting it into a string but it still gives me the same output. Any ideas?

Note: If I have more than one value in my businessUnitValidList, the numbers show up the way they are supposed to.

EDIT

This is the current code iteration:

<cfloop array="#businessUnitArray#" index="i">

   <cfquery name="validatebusinessUnit" datasource="dbproduction">
       select doctorid from survey.dbo.clientLocationMap
       where clientbrandid = '#arguments.clientBrandid#'
       and clientLocation = '#i#'
   </cfquery>

   <cfif validatebusinessUnit.recordcount gt 0>
       <cfset businessUnitValidList = listAppend(businessUnitValidList,toString(validatebusinessUnit.doctorid),",")>
   <cfelse>
       <cfset businessUnitInValidList = listAppend(businessUnitInValidList,i,",")>
   </cfif>

</cfloop>

<cfif businessUnitInValidList neq ''>
    <cfset ResponseStruct['BusinessUnitCodes']['businessUnitMixResponse']['businessUnitInValidList'] = "#businessUnitInValidList#">
    <cfset ResponseStruct['BusinessUnitCodes']['businessUnitMixResponse']['businessUnitValidList'] = "#businessUnitValidList#">
    <cfreturn serializeJSON(ResponseStruct['BusinessUnitCodes']['businessUnitMixResponse'])>
<cfelse>
    <cfset ResponseStruct['BusinessUnitCodes']['businessUnitSuccess']['businessUnitValidList'] = "#businessUnitValidList#">
    <cfreturn serializeJSON(ResponseStruct['BusinessUnitCodes']['businessUnitSuccess'])>
</cfif>
Leigh
  • 28,765
  • 10
  • 55
  • 103
Geo
  • 3,160
  • 6
  • 41
  • 82
  • 1
    How are we supposed to help you when you have not provided the code that is having this issue? You have been on here long enough to know how to ask appropriate questions - [mcve] – Miguel-F Mar 17 '16 at 11:47
  • 4
    Try converting it to a string and including a leading space; e.g. "businessUnitValidList":" 2003051509034372557922" – Jedihomer Townend Mar 17 '16 at 11:50
  • You can also try using the `PrecisionEvaluate()` function on it - http://stackoverflow.com/a/16968077/1636917 – Miguel-F Mar 17 '16 at 11:55
  • @Miguel-F I know what you mean but my code is producing the correct outcome is the deserializeJSON() that is causing the issue. – Geo Mar 17 '16 at 13:01
  • @JedihomerTownend I tred that as well and did not work – Geo Mar 17 '16 at 13:01
  • Which version/update level of ColdFusion are you running? There have been numerous issues with serializeJSON() and deserializeJSON() that have either been fixed in later versions/updates or haven't been fixed yet. And apparently this is one of the bugs that hasn't been fixed yet: https://bugbase.adobe.com/index.cfm?event=bug&id=4106369 – Carl Von Stetten Mar 17 '16 at 14:22
  • @CarlVonStetten 11,0,07,296330 – Geo Mar 18 '16 at 09:11
  • @JedihomerTownend - You should have posted that as an "answer" :-) – Leigh Mar 19 '16 at 01:00
  • @Geo - *RE: my code is producing the correct outcome* No, it is not. See my response below. – Leigh Mar 19 '16 at 02:18

3 Answers3

7

ColdFusion's JSON serialization has issues and can vary between versions and even hotfixes. As Jedihomer Townend mentioned in the comments a leading space should force CF to treat it a string and not cast it.

I've just tried this on CF10, 11 and 2016 and preserves the input.

<cfscript>
a = {
  "businessUnitValidList":" 2003051509034372557922",
  "shortMessage":"Success",
  "longMessage":"Request Completed Successfully.",
  "status":20001
};
json = serializeJSON(a);
b = deserializeJSON(json);

writeDump(b);
</cfscript>

You can try it here:

http://trycf.com/gist/70b86fbb57f752125f35/acf?theme=monokai

John Whish
  • 3,016
  • 17
  • 21
  • I see that you have a space in front of the number and it is converted to a string. When I do that I have no issues. The problem is when the `"businessUnitValidList":" 2003051509034372557922",` is like this `"businessUnitValidList":2003051509034372557922,` – Geo Mar 18 '16 at 09:13
  • 1
    Yeah, it's because the number is too big for ColdFusion to cope with, so it converts it to scientific notation. By forcing it to treat it as a string then it won't convert it - it's a trick/hack really but it's the only option I think you have here. – John Whish Mar 18 '16 at 09:46
5

(Too long for comments)

my code is producing the correct outcome is the deserializeJSON() that is causing the issue.

Not quite. The JSON value is correct, but the serialization omits the surrounding quotes. That means the value will be handled as a numeric type during deserialization, causing the issue you observed. Unless you can force the serialization to treat the value as a string, the deserialized result will always be wrong. As Carl Von Stetten already mentioned, it is a bug. Jedihomer Townend's answer of appending a space character is probably the simplest work-around.

Longer answer:

Despite the improvements in CF11's JSON handling, CF is still a little too "helpful" ... As you noted, CF detects the value is numeric when serializing and omits the surrounding quotes. Consequently marking the value type as numeric.

{..."businessUnitValidList":2003051509034372557922 } 

That all sounds great, until you try and deserialize. If the value was enclosed in quotes, it would be handled as a string, and the original value preserved. Unfortunately, without the quotes it is considered numeric, which means CF must stuff the value into one of its two numeric data types:

  1. Real or floating point number, ie java.lang.Double or
  2. 32 bit Integer, ie java.lang.Integer

The maximum value of an Integer is 2147483647. Obviously your number is too large for that, so CF converts it into a java.lang.Double instead. That is a problem for two reasons. First, Double is an approximate type. Second, according to the rules of that class, scientific notation may be used when representing the number as a String, ie when the variable is displayed with cfoutput or cfdump. That is why the deserialized result looks different than what you were expecting. Unless you can force it to be treated as a string when serialized, the deserialized result will always be wrong.

In fairness, CF11 does contain a number improvements for JSON handling. Unfortunately most of them revolve around cfc's and query objects. Given your current structure, it won't quite work. However, if you were able to use a single query object you could resolve the issue by with the help of the new application level setting this.serialization.serializeQueryAs = "struct";. It forces a more sensible format for serialized queries than in earlier versions. Since CF11 respects column data types when serializing, the value would be preserved if the column data type is BIGDECIMAL, or you cast it as a VARCHAR. Unfortunately, CF still upper cases query column names, but the base values are preserved.

Result:

[ { "BUSINESSUNITVALIDLIST" : "2003051509034372557922",
    "LONGMESSAGE" : "Request Completed Successfully.",
    "SHORTMESSAGE" : "Success",
    "STATUS" : 20001
  } ]

Code:

qry = queryNew("");
queryAddColumn(qry, "businessUnitValidList", "varchar", ["2003051509034372557922"]);
queryAddColumn(qry, "shortMessage", "varchar", ["Success"]);
queryAddColumn(qry, "longMessage", "varchar", ["Request Completed Successfully."]);
queryAddColumn(qry, "status", "integer", [20001]);

json = serializeJSON(qry);
writeDump(deserializeJSON(json));

CF11 also introduced custom serializers/deserializers, which might work here. Though it is probably overkill for this specific task.

Having said all that, again the simplest option is to use the "append a non-numeric character" hack. Well .. either that or switch to a custom library which may do a more consistent job with JSON handling ;-)

Side Note / Efficiency:

Unless there is a specific reason you must execute a query within a loop, there are likely more efficient options (dbms specific, which you did not mention). Also, do not forget to use cfqueryparam on all variable query parameters. Among it is many benefits is boosting performance when the same query is executed multiple times - such as inside a loop.

Community
  • 1
  • 1
Leigh
  • 28,765
  • 10
  • 55
  • 103
4

In java we have BigInteger data type to store big values. In Coldfusion we can cast by using JavaCast to integer, long and double. But the problem with this is that even with 'long' datatype can only store 19 digits.

And by converting number to string it may be problematic to perform mathematical operations. Here we can use precisionEvaluate() while performing mathematical operations.

PrecisionEvaluate function lets you calculate arbitrarily long decimal (BigDecimal precision) values. BigDecimal precision arithmetic accepts and generates decimal numbers of any length.

In this example you can use like this :

<cfset ResponseStruct['BusinessUnitCodes']['businessUnitMixResponse']['businessUnitInValidList'] = "#precisionEvaluate(businessUnitInValidList)#">

Leigh
  • 28,765
  • 10
  • 55
  • 103
Santosh D.
  • 537
  • 6
  • 19
  • *converting number to string it may be problematic to perform mathematical operations* True, but the JSON representation of it must be a String. Otherwise, when deserializing CF will convert it into one of two numeric data types: 32 bit Integer (too small) or Double (causing scientific notation). Even if you use precisionEvaluate on the way in. – Leigh Mar 19 '16 at 02:09