The built-in JSON class that debuted with Flash Player 11 has an interesting feature that few AS3 programmers know about. It turns out that JSON.parse doesn’t just take the JSON document to parse but also a “reviver” Function. What is this? How can it be used? Find out more in today’s article and take advantage of this powerful parsing option.

According to the documentation, the optional “reviver” parameter to JSON.parse is as follows:

The reviver parameter is a function that takes two parameters: a key and a value. You can use this function to transform or filter each key/value pair as it is parsed. If you supply a reviver function, your transformed or filtered value for each pair, rather than the default parsing, is returned in the parse() function output. If the reviver function returns undefined for any pair, the property is deleted from the final result.

Now let’s take a look at a tiny app that demonstrates kinds of calls your reviver function can expect:

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.utils.describeType;
 
	public class ReviverFunction extends Sprite
	{
		public function ReviverFunction()
		{
			var logger:TextField = new TextField();
			logger.defaultTextFormat = new TextFormat("_typewriter", 10);
			logger.width = stage.stageWidth;
			logger.height = stage.stageHeight;
			addChild(logger);
			function log(msg:String): void { logger.appendText(msg+"\n"); }
 
			var str:String = '{'
				+ '"students":[' + 
					'{"first":"Sara", "last":"Smith"},' + 
					'{"first":"Mike", "last":"Thompson"}' +
				'],' +
				'"teachers":[' +
					'{"first":"Greg", "last":"Miller"},' +
					'{"first":"Kara", "last":"Hernandez"}' +
				']' +
			'}';
			var obj:Object = JSON.parse(
				str,
				function(key:String, val:Object): * {
					if (val is String)
					{
						log('"' + key + '" = "' + val + '"');
					}
					else if (val is Array)
					{
						log('"' + key + '" = [');
						var arr:Array = val as Array;
						log("LENGTH: " + arr.length);
						for (var i:int = 0; i < arr.length; ++i)
						{
							var cur:* = arr[i];
							log("    " + cur);
						}
						log("]");
					}
					else
					{
						log('"' + key + '" = {');
						for (var k:* in val)
						{
							log("    " + key + ": " + val[k]);
						}
						log("}");
					}
				}
			);
		}
	}
}

Here is the output of this program:

"first" = "Sara"
"last" = "Smith"
"0" = {
}
"first" = "Mike"
"last" = "Thompson"
"1" = {
}
"students" = [
LENGTH: 2
    undefined
    undefined
]
"first" = "Greg"
"last" = "Miller"
"0" = {
}
"first" = "Kara"
"last" = "Hernandez"
"1" = {
}
"teachers" = [
LENGTH: 2
    undefined
    undefined
]
"" = {
}

Several aspects of this output are strange, so it may not be what you were expecting to see. For example, the elements of the arrays are passed to the reviver function with String keys for their indices rather than integers. Since AS3 automatically converts between strings and indices this may not be much of a problem, but it’s a bit odd.

Much more strange is that these arrays have the appropriate lengths but their contents are completely undefined. For example, the “teachers” array has the appropriate length of two but the elements at index 0 and 1 are both undefined.

Similarly, all objects (e.g. the students) have no elements within them. It’s not shown in the above source code, but if you try to manually access the fields (e.g. val["first"] or val.first) you’ll still get undefined.

This means that the only interesting values your reviver function gets are of type String. It may still be interesting to know how long an Array is or that something is an Object but the contents of those types will remain a mystery to your reviver function.

Armed with that information, what can we do with the reviver function? Well, we could follow the documentation’s recommendation and transform the input. Here’s a case where we capitalize the students’ and teachers’ names:

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.utils.describeType;
 
	public class ReviverFunctionUpper extends Sprite
	{
		public function ReviverFunctionUpper()
		{
			var logger:TextField = new TextField();
			logger.defaultTextFormat = new TextFormat("_typewriter", 10);
			logger.width = stage.stageWidth;
			logger.height = stage.stageHeight;
			addChild(logger);
			function log(msg:String): void { logger.appendText(msg+"\n"); }
 
			var str:String = '{'
				+ '"students":[' + 
					'{"first":"sara", "last":"smith"},' + 
					'{"first":"mike", "last":"thompson"}' +
				'],' +
				'"teachers":[' +
					'{"first":"greg", "last":"miller"},' +
					'{"first":"kara", "last":"hernandez"}' +
				']' +
			'}';
			var obj:Object = JSON.parse(
				str,
				function(key:String, val:Object): * {
					if (key == "first" || key == "last")
					{
						return val.charAt(0).toUpperCase() + val.substr(1);
					}
					return val;
				}
			);
 
			for each (var person:Object in obj.students.concat(obj.teachers))
			{
				log(person.first + " " + person.last);
			}
		}
	}
}

Output:

Sara Smith
Mike Thompson
Greg Miller
Kara Hernandez

Or you could filter out unwanted values. Here’s a case where a maximum last name length is set to 5 and everybody else is rejected:

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.utils.describeType;
 
	public class ReviverFunctionShort extends Sprite
	{
		public function ReviverFunctionShort()
		{
			var logger:TextField = new TextField();
			logger.defaultTextFormat = new TextFormat("_typewriter", 10);
			logger.width = stage.stageWidth;
			logger.height = stage.stageHeight;
			addChild(logger);
			function log(msg:String): void { logger.appendText(msg+"\n"); }
 
			var str:String = '{'
				+ '"students":[' + 
					'{"first":"Sara", "last":"Smith"},' + 
					'{"first":"Mike", "last":"Thompson"}' +
				'],' +
				'"teachers":[' +
					'{"first":"Greg", "last":"Miller"},' +
					'{"first":"Kara", "last":"Hernandez"}' +
				']' +
			'}';
			var obj:Object = JSON.parse(
				str,
				function(key:String, val:Object): * {
					if ((key == "first" || key == "last") && val.length > 5)
					{
						return undefined;
					}
					return val;
				}
			);
 
			for each (var person:Object in obj.students.concat(obj.teachers))
			{
				log(person.first + " " + person.last);
			}
		}
	}
}

Output:

Sara Smith
Mike undefined
Greg undefined
Kara undefined

Well that didn’t really work. How could we exclude these long-named students? Unfortunately, the reviver function isn’t really up the task in this case. We’d need to restructure our JSON document to accommodate for the strict, contextless key-value pair nature of the reviver function. In this case we merge the names into one field so we can reject them all at once rather than rejecting just the first or last name:

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.utils.describeType;
 
	public class ReviverFunctionShort2 extends Sprite
	{
		public function ReviverFunctionShort2()
		{
			var logger:TextField = new TextField();
			logger.defaultTextFormat = new TextFormat("_typewriter", 10);
			logger.width = stage.stageWidth;
			logger.height = stage.stageHeight;
			addChild(logger);
			function log(msg:String): void { logger.appendText(msg+"\n"); }
 
			var str:String = '{'
				+ '"students":[' + 
					'"name:Sara_Smith",' + 
					'"name:Mike_Thompson"' +
				'],' +
				'"teachers":[' +
					'"name:Greg_Miller",' +
					'"name:Kara_Hernandez"' +
				']' +
			'}';
			var obj:Object = JSON.parse(
				str,
				function(key:String, val:Object): * {
					if (val is String && val.indexOf("name:") == 0)
					{
						var name:String = val.substr("name:".length);
						var parts:Array = name.split("_");
						if (parts[1].length > 5)
						{
							return undefined;
						}
						return {first:parts[0], last:parts[1]};
					}
					return val;
				}
			);
 
			for each (var person:Object in obj.students.concat(obj.teachers))
			{
				log(person.first + " " + person.last);
			}
		}
	}
}

Output:

Sara Smith

This allows us to get the desired result, but now we’ve written a lot more parsing code and lost many of the benefits of the automatic parsing that we get with the JSON parser. Still, it’s a way to do some light filtering of the JSON data. If you want to do something more complex like parse the JSON document to an instance of some AS3 class you have, Adobe has the gory details. It’s not for the feint of heart.

As an alternative, one that isn’t human-readable or supported by as many apps but it much better compressed, consider AMF as discussed in these articles:

Spot a bug? Have a question or suggestion? Post a comment!