"Strict" decision functions?

JSON extension

ValidateJson()

1 Like

So XMLValidate and ValidateJSON ? What is this?? PHP??

2 Likes

php? god no, our documentation isn’t based on reading comments on the docs about which of the 27 different functions you can use to parse xml, we have jira for that :upside_down_face:

For what it’s worth, I tried to add some illustrations as to how the loosely typed nature of the decision functions don’t always jive with the modernized features like member methods:

1 Like

I have been using a “DataMapper” for a long time which converts any type (cf or java) of data into something else.
It’s a little old but it always works great.

It is based on an .xml file that specifies the type of data arriving and what you want to obtain.

Something like this:

Conf file:

<mappers>

    <mapper id="Document" source="Cf:Struct" target="Cfc:tests.bean.Document">
        <map from="document_id" to="id" type="cf:String" />
        <map from="abbreviation" to="abbr" type="cf:String" />
        <map from="available" to="available" type="cf:Boolean" default="true" />
        <map from="Author" to="Author" ref="Author" />
    </mapper>

    <mapper id="Author" source="Cf:Struct" target="Cfc:tests.bean.Author">
        <map from="author_id" to="id" type="cf:String" />
        <map from="name" to="name" type="cf:String" />
    </mapper>

    <!-- test for helper -->
    <mapper id="DocumentToEncrypt" source="Cf:Struct" target="Cfc:tests.bean.Document">
        <map from="document_id" to="id" type="cf:String" helper="securityHelper:encryptData" invertHelper="securityHelper:decryptData" />
        <map from="abbreviation" to="abbr" type="cf:String" />
        <map from="available" to="available" type="cf:Boolean" default="true" />
        <map from="Author" to="Author" ref="Author" />
    </mapper>

</mappers>

then:

// Initialize the DataMapper with the configuration file.
<cfset dm = new dataMapper( ExpandPath('ConfigDataMapper.xml') )>

// convert data (struct) to bean (obj) using "Document" mapper. 
<cfset bean = dm.convert( data, "Document" )>

// reverse: convert bean (obj) to data (struct)
<cfset data = dm.convert( bean, "Document", true )>

This way I can:

  • check the fields to export
  • be sure that the data is always consistent (I hate to have “1.00” when it should be: 1)

If it helps, I can make the code available

3 Likes

I read this with interest because I am in the process of migrating a legacy CF10 app to Lucee (my first experience of Lucee). In my case the issue arose for SerializeJSON() where the Lucee app uses Taffy and provides JSON packets to a JS client app that (rightly or wrongly) expects booleans to be booleans. eg "myflag": true, not "myflag":"true"

My discussion response here is more about separation of concerns but relates to type checking as well.

( @bennadel I have been using a slightly customised version of your JsonSerializer.cfc in the CF10 app but many of the reasons for using it are removed with the Lucee SerializeJSON. So, thanks for your long service! :wink: )

However, three things that are not supported in SerializeJSON, that the client JS app is expecting, are:

  1. convert all keys to lowercase;
  2. convert string boolean to actual boolean;
  3. convert cf null / undefined to an empty string

I am using Taffy 3.6 so all the serialization happens in one place in a Custom Serializer that pre-processes the data-to-be-serialized to make the adjustments above.
See Topic 10619 and Topic 10636 for my workaround and language proposals.

So, the pre-processor does this (minimal code):

{
    // Given arguments.key and arguments.value (a simple type) ...
    
    // First, convert any nulls to an empty string
    var val = isNull(arguments.value) ? "" : arguments.value;

    // Convert string booleans to real booleans
    if ( isInstanceOf(val, "java.lang.String") && (compare(val, "true") == 0 or compare(val, "false") == 0)) {
      val = (val eq "true");
    }

    // Convert key to lowercase
    result.append( { "#lcase(arguments.key)#": val } );
} 

My conversion of string boolean to real boolean works but is verbose.

The point is: I support Ben’s idea of specific “strict” functions and separately the idea that if the business logic can work fine without strict types and the issue is serialization then the serializer should be concerned about type, not the business logic.

What if some other client app needs JSON encoded with booleans in the opposite form ie as strings not booleans? Or keys as upper case?

In my case, I have many examples of (arrays of) structs generated from some sql / hql / direct construction that ends up with a property like s1.myflag = "true" and that property is used in the Business Logic as a “thruthy” / boolean for the purposes of the logic. All good. And when Lucee serializes that it gets serialized as a string, which is correct, but is not what I need for the client JS app - it needs to be serialized as a boolean for the JS, as described above.

In my case a set of “strict” functions would slightly expedite things. In another serialization use-case other strict functions might be more relevant.

The overall point is IMJ this should be done in the serializer, not in the Business Logic, to enable easier servicing of client needs especially where you can’t, or don’t want to, change the client to suit your CFML code! :wink:

Just my two cents worth.
Murray

3 Likes

@flowt-au when we jumped from ACF 10 to Lucee 5, using the native serialization instead of the JsonSerializer.cfc was such a happy day! It’s funny, though, we have a lot of workarounds as well, like you do. In fact, much of the old part of the app still use JsonSerializer.cfc; but, all the new parts use the native stuff. But, all the actual serialization is done in a centralized location, so we have to set an rc value telling the framework which to use. Our code looks like this:

var jsonPayload = ( rc.useNativeSerialization )
   ? serializeJson( rc.apiResponse.data )
   : jsonSerializer.toSafeJson( rc.apiResponse.data )
;

As far as where in the code should be responsible for the type-validation, this whole thread has given me a lot to think about; but, I am not certain how I feel. If we defer to the serialization portion of the code, then any code higher in the stack that tries to use a value that is an unexpected type (ex, calling value.yesNoFormat() on a “String”, not a “Boolean”) will explode.

I’m still rolling this all over in my head.

And I’d have an update for you if you were to… respond to my ping on Discord… :wink:

blows dust off Discord app

Yes, I see. I guess I see them as two separate “concerns”. If the business logic needs to have strict types, then yes, of course do it there. But if the serializer is the only thing that cares, it can be deferred to there, I reckon. I guess I am saying it is all about the use-case and having both options is a good thing. So yes, having the ability to do strict type checking where-ever you need it, is good.

1 Like

Another thought: given the rise of TypeScript for JS, there is clearly a desire for strong types. And Java already is typed. So, a set of strict functions could allow a pathway to that outcome for those that want to go down that path.

ColdFusion has always been build your own “___” framework. If someone wanted to, and they had the time, talent and or resources they could build something like brython in pure ColdFusion

that being said, the coldbox framework is a lot like typescript in that it is a methodogy and a subset of ColdFusion.

I would love to see something that takes ColdFusion, turns it into pure “flavor of the week”. if done correctly, it could be a drop in template to extend ColdFusion to every programming language. If done incorrectly, it could be much like Macromedia flash paper.

For general purpose usage of CFML we have Lucee’s JSR-223 scripting engine implementation. That is java specific. For browser-based CFML we’d need an LSP. I’d love to work on something like that but it sounds like a boat load of work and I know from CommandBox, it’s hard to package up Lucee to be small and have a fast startup.

LSP?

Not familiar with the term.

LSP = Language Server Protocol.

1 Like

Yep, sorry. It basically is a way to embed any language into a browser or an IDE. Adobe claimed they were going to be writing one for their new VSCode-based CF Builder, but we’ll see if they actually did (I’m not betting on it)

1 Like

If they did, they just pasted Monaco Editor and loaded the old cfeclipse jar in.

1 Like

Good proposal, I think.
But, from a design point of view, isStrictBoolean and isStrictNumeric are, respectively, inferior to isBoolean(myVar, true) and isNumeric(myVar, true).

Some reasons:

  1. Methods describe behaviour. IsBoolean and isNumeric are existing functions. Adding “Strict” is equivalent to qualifying known behaviour. Therefore, “Strict” should come in as an argument rather than as a prefix; we’re not dealing with new behaviour.

  2. As isBoolean and isNumeric are existing functions, adding a second argument is a natural extension, providing backward-compatibility.

2 Likes

I’m not against anything you said here. I could definitely be swayed in that direction.

also, this way could allow you to set the default in the admin or as server or application setting ?