Skip to content

Commit

Permalink
Allow closest key to be passed-down through serialization process suc…
Browse files Browse the repository at this point in the history
…h that arrays of simple-values can be type-cast.
  • Loading branch information
bennadel committed Aug 12, 2013
1 parent a04ac48 commit c3dc6b2
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 77 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,12 @@ given the explicitly defined casing. So, if you want to use "id" in one place
and "ID" in another place within the same data-structure, you're out of luck.
Both keys will match "id" and will be given the same case.

## API Philosophy

This is primarily intended to be used to return data from a server-side API. As
part of that use-case, some of my philosophy is baked into it. Namely, an API
usually returns a top-level struct / hash-map that defines the API result. This
is why the serialization process is driven by the name of keys.


[1]: http://www.bennadel.com
2 changes: 2 additions & 0 deletions example/index.cfm
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
.asAny( "createdAt" )
.asDate( "dateOfBirth" )
.asString( "favoriteColor" )
.asInteger( "favoriteNumbers" )
.asString( "firstName" )
.asString( "lastName" )
.asString( "nickName" )
Expand All @@ -23,6 +24,7 @@
DATEOFBIRTH = dateConvert( "local2utc", "1975/01/01" ),
NICKNAME = "Trish",
FAVORITECOLOR = "333333",
FAVORITENUMBERS = [ true, 4.0, 137, false ],
AGE = 38,
CREATEDAT = now(),
PASSWORD = "I<3ColdFusion&Cookies"
Expand Down
179 changes: 102 additions & 77 deletions lib/JsonSerializer.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,18 @@ component

// I prepare the given array for serialization. Since the array doesn't have keys, this
// function will simply walk the array and prepare each value contained within the array.
private array function prepareArrayForSerialization( required array input ) {
private array function prepareArrayForSerialization(
required array input,
required string closestKey
) {

var preparedInput = [];

for ( var value in input ) {

arrayAppend(
preparedInput,
prepareInputForSerialization( value )
prepareInputForSerialization( value, closestKey )
);

}
Expand All @@ -193,27 +196,38 @@ component


// I prepare the input for case/value-sensitive serialization.
private any function prepareInputForSerialization( required any input ) {
private any function prepareInputForSerialization(
required any input,
string closestKey = ""
) {

// Convert the response based on its type.
if ( isArray( input ) ) {

return(
prepareArrayForSerialization( input )
prepareArrayForSerialization( input, closestKey )
);

} else if ( isStruct( input ) ) {

// NOTE: No need to pass-in the closestKey since struct will provide its own keys.
return(
prepareStructForSerialization( input )
);

} else if ( isQuery( input ) ) {

// NOTE: No need to pass-in the closestKey since query will provide its own keys.
return(
prepareQueryForSerialization( input )
);

} else if ( isSimpleValue( input ) ) {

return(
prepareSimpleValueForSerialization( input, closestKey )
);

}

// If the input is not a complex type, then we can gain no insight on how it's supposed
Expand All @@ -224,76 +238,19 @@ component
}


// I prepare the key-value pair for use in the prepared input. I will attempt to convert the
// value into the serialization-specific data type before adding it to the input.
private struct function prepareKeyValuePairForSerialization(
required struct preparedInput,
required string key,
required any value
) {

// If this key has been blocked, just return the unaltered input.
if ( structKeyExists( blockedKeyList, key ) ) {

return( preparedInput );

}

// Now that we know this key isn't blocked, get the case-sensitive version of it as
// defined in our key list. If the key has not been defined, we'll use lowercase as
// the default formatting.
var preparedKey = (
structKeyExists( fullKeyList, key )
? fullKeyList[ key ]
: lcase( key )
);

// Check to see if the key was defined in a data-type-specific list. If so, we'll try to
// convert the value as we copy it over into the prepared input.
if (
structKeyExists( integerKeyList, key ) &&
isNumeric( value )
) {

var preparedValue = javaCast( "long", value );

} else if (
structKeyExists( floatKeyList, key ) &&
isValid( "float", value )
) {
// Return the key with the appropriate casing (or all lowercase if no case has been provided).
private string function prepareKeyForSerialization( required string key ) {

var preparedValue = javaCast( "float", value );

} else if (
structKeyExists( booleanKeyList, key ) &&
isBoolean( value )
) {

var preparedValue = javaCast( "boolean", value );

} else if (
structKeyExists( dateKeyList, key ) &&
isNumericDate( value )
) {

var preparedValue = getIsoTimeString( value );

} else if ( isSimpleValue( value ) ) {
if ( structKeyExists( fullKeyList, key ) ) {

// Prepend the string-value with the start-of-string marker so that ColdFusion won't
// be tempted to serialize the string value as a number.
var preparedValue = ( START_OF_STRING & value );
return( fullKeyList[ key ] );

} else {

var preparedValue = prepareInputForSerialization( value );

}
return( lcase( key ) );

// Add the prepared key/value pair into the prepared input.
preparedInput[ preparedKey ] = preparedValue;

return( preparedInput );
}

}

Expand Down Expand Up @@ -328,11 +285,17 @@ component

for ( var key in listToArray( input.columnList ) ) {

prepareKeyValuePairForSerialization(
preparedInput,
key,
input[ key ][ rowIndex ]
);
// If this key is black-listed, skip it.
if ( structKeyExists( blockedKeyList, key ) ) {

continue;

}

// Get the appropriate casing for the key.
var preparedKey = prepareKeyForSerialization( key );

preparedInput[ preparedKey ] = prepareInputForSerialization( input[ key ][ rowIndex ], key );

}

Expand All @@ -348,11 +311,17 @@ component

for ( var key in input ) {

prepareKeyValuePairForSerialization(
preparedInput,
key,
input[ key ]
);
// If this key is black-listed, skip it.
if ( structKeyExists( blockedKeyList, key ) ) {

continue;

}

// Get the appropriate casing for the key.
var preparedKey = prepareKeyForSerialization( key );

preparedInput[ preparedKey ] = prepareInputForSerialization( input[ key ], key );

}

Expand All @@ -361,6 +330,62 @@ component
}


// I prepare the given simple value for serialization by converting (or attempting to convert
// it) into the data type defined by the closest key in the contextual data structure.
private any function prepareSimpleValueForSerialization(
required any value,
required string closestKey
) {

// If we don't have any known container key, then we have no extra insight into how to
// serialize this value. As such, force it to be a string.
if ( closestKey == "" ) {

return( START_OF_STRING & value );

}

// Check to see if the key was defined in a data-type-specific list. If so, we'll try to
// convert the value as we copy it over into the prepared input.
if (
structKeyExists( integerKeyList, closestKey ) &&
( isNumeric( value ) || isBoolean( value ) )
) {

return( javaCast( "long", value ) );

} else if (
structKeyExists( floatKeyList, closestKey ) &&
( isNumeric( value ) || isBoolean( value ) )
) {

return( javaCast( "float", value ) );

} else if (
structKeyExists( booleanKeyList, closestKey ) &&
isBoolean( value )
) {

return( javaCast( "boolean", value ) );

} else if (
structKeyExists( dateKeyList, closestKey ) &&
isNumericDate( value )
) {

return( getIsoTimeString( value ) );

} else {

// Prepend the string-value with the start-of-string marker so that ColdFusion won't
// be tempted to serialize the string value as a number.
return( START_OF_STRING & value );

}

}


// I strip out the start-of-string markers that were used to force ColdFusion to serialize
// the given value as a string (ie, blocks accidental numeric conversions).
private string function removeStartOfStringMarkers( required string response ) {
Expand Down
15 changes: 15 additions & 0 deletions tests/specs/JsonSerializerTest.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ component
.asInteger( "id" )
.asString( "lastName" )
.exclude( "password" )
.asBoolean( "quizAnswers" )
.asFloat( "rating" )
;

Expand Down Expand Up @@ -69,6 +70,10 @@ component

user.favoriteColors = colors;

quizAnswers = [ 1, 1, 0, 1 ];

user.quizAnswers = quizAnswers;

}


Expand Down Expand Up @@ -152,6 +157,7 @@ component
assertUserValues( serializedInput );
assertMovieValues( serializedInput );
assertColorValues( serializedInput );
assertQuizAnswerValues( serializedInput );

}

Expand All @@ -165,6 +171,7 @@ component
assertUserValues( serializedInput );
assertMovieValues( serializedInput );
assertColorValues( serializedInput );
assertQuizAnswerValues( serializedInput );

}

Expand Down Expand Up @@ -236,6 +243,14 @@ component
}


public void function assertQuizAnswerValues( required string serializedInput ) {

// Test the values.
assert( find( "true,true,false,true", serializedInput ) );

}


public void function assertUserValues( required string serializedInput ) {

// Test the keys.
Expand Down

0 comments on commit c3dc6b2

Please sign in to comment.