AMF vs. JSON in AIR Mobile Applications

UPDATE 11/23/2011: Full source code now available at:
https://github.com/triceam/Flex-Mobile-Serialization-Tester


Recently, I’ve been asked more than once which is better: AMF or JSON for AIR mobile applications. This post is to highlight some performance comparisons, and a sample testing application that I put together. First, it is important to know what both AMF and JSON are.

AMF

Action Message Format (AMF) is a compact binary format that is used to serialize
ActionScript object graphs. Once serialized an AMF encoded object graph may be used
to persist and retrieve the public state of an application across sessions or allow two
endpoints to communicate through the exchange of strongly typed data.

-from the AMF3 Specficiation

JSON

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language, Standard ECMA-262 3rd Edition – December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others.

-from www.json.org/

Both AMF and JSON are compact serialization formats and provide efficient data transport.  The main differences between the two formats are as follows:

  • AMF is a binary format that is not easily readable by humans, JSON is a text-based format that is easily readable.
  • AMF allows for serialization of strongly typed objects in transactions between the client and server, JSON only supports generic or loosely-typed objects.

Former Adobe Evangelist James Ward put together a suite of benchmarks comparing JSON, SOAP, and AMF that show comparable performance between AMF and JSON.   Recently, AIR 3.0 and Flash Player 11 brought native JSON support, which greatly improves JSON parsing in Flash & AIR runtimes.   This is a huge boost, especially for mobile applications that consume JSON data.

I put together a very basic test case where a mobile application makes requests of simple data objects from a ColdFusion CFC.  In each test iteration, a request is made for 1, 10, 100, 1000, and 10000 value objects, in both AMF and JSON formats. The total round trip time from request to deserialization is measured and compared for each case, for a total of 5 iterations through each cycle.   My findings are that AMF and JSON have comparable performance in smaller record sets.  However, AMF seems to have better performance as data sets grow.  In my test cases, the 1000+ record results were consistently faster using AMF.  However, in smaller data sets, JSON was often faster (however not consistently, or by much of a margin). I tested these times on both an iPhone 4 and Motorolla Atrix, both running on the carrier networks (not over wifi).

Below is a video of the serialization testing application at work.

Here are a few screenshots of the application.

The Tests

For these tests I created two basic CFCs (ColdFusion Components). One is a simple data value object. The other CFC is a gateway to expose a remote service that returns the value objects to the client. I chose a ColdFusion CFC for this case b/c it can easily be serialized as AMF or JSON just by changing the endpoint used to consume the service.

Here is the basic value object CFC:

component {

	property name="itemId";
	property name="value1";
	property name="value2";
	property name="value3";

	this.itemId = 0;
	this.value1 = CreateUUID();
	this.value2 = CreateUUID();
	this.value3 = CreateUUID()
}

Here is the service CFC used to return data to the client:

component {

	remote array function getRecords(numeric records=1) {
    	var result = [];

        for (var x = 0; x < records; x=x+1) {
        	var item = new SampleVO();
            item.itemId = x;
            ArrayAppend( result, item );
        }

        return result;
    }
}

Obviously, this is a fictional data object with randomly generated values. However, it still represents a reasonable service payload for data serialization. By accessing the data via the ColdFusion Flex/Remoting gateway, you access the remote services via AMF3.

remoteObject = new RemoteObject("ColdFusion");
remoteObject.source = "com.tricedesigns.mobileTest.Services";
remoteObject.endpoint = "http://tricedesigns.com/flex2gateway/";

var token : AsyncToken = remoteObject.getRecords( RECORD_COUNT[ recordCountIndex ] );
token.addResponder( new mx.rpc.Responder( onAMFResult, onFault ) );

By accessing the data via an http endpoint, with returnformat=josn, you will invoke the same CFC remote method exposed as JSON.

httpService = new HTTPService();

httpService.url = "http://tricedesigns.com/com/tricedesigns/mobileTest/Services.cfc?method=getrecords&records=" + RECORD_COUNT[ recordCountIndex ] + "&returnformat=json";
var token : AsyncToken = httpService.send();
token.addResponder( new mx.rpc.Responder( onJSONResult, onFault ) );

The JSON-formatted data will look something like this:

[{"ITEMID":0,"VALUE3":"FA817ED6-EB7C-0677-097452161BCB6689","VALUE2":"FA817ED5-0FC4-BD8B-6515B283E5426AAC","VALUE1":"FA817ED4-0B3A-71B4-D45559FBB0AE5BEE"},
{"ITEMID":1.0,"VALUE3":"FA817ED9-FBBE-B9A2-9C01390B65B65DDB","VALUE2":"FA817ED8-A5EF-EE8E-72692303F9C5CFCB","VALUE1":"FA817ED7-D569-2008-A5BFB9F6E1154FE6"},
{"ITEMID":2.0,"VALUE3":"FA817EDC-FC4E-3473-6FC7910831CB293A","VALUE2":"FA817EDB-DF92-71D5-B6B5C67EC93816DD","VALUE1":"FA817EDA-90A3-1566-96FC2524628DCB56"},
{"ITEMID":3.0,"VALUE3":"FA817EDF-923A-DC19-07128DF719212B97","VALUE2":"FA817EDE-E59F-40F0-FE3A9267DE952E8E","VALUE1":"FA817EDD-B0F0-5B20-675E0B0A61D4DA46"},
{"ITEMID":4.0,"VALUE3":"FA817EE2-CDCA-5C3D-88D3B72EEF11AA60","VALUE2":"FA817EE1-99D3-741D-58F5BA5DC00C035F","VALUE1":"FA817EE0-F1AB-0AEF-2FC57BA2104FB365"},
{"ITEMID":5.0,"VALUE3":"FA817EE5-0750-E4A5-18914030A5EC4BF2","VALUE2":"FA817EE4-07A4-D025-16BF02A7452F3EC2","VALUE1":"FA817EE3-E72B-B8CA-F22607314115CACF"},
{"ITEMID":6.0,"VALUE3":"FA817EE8-AC14-79D7-6F2BF568CE172823","VALUE2":"FA817EE7-95B8-9BA8-9265B6BAFF927D48","VALUE1":"FA817EE6-FCD4-2998-965667E97F515AB5"},
{"ITEMID":7.0,"VALUE3":"FA817EEB-E5F2-F2AB-FE68B85311E126B0","VALUE2":"FA817EEA-C93B-65FF-C7867A6A097BA1FC","VALUE1":"FA817EE9-B009-EC7E-BA063963F0E905E9"},
{"ITEMID":8.0,"VALUE3":"FA817EEE-C060-FD2E-B611E38AC454A789","VALUE2":"FA817EED-BED5-79F3-E6F72A823B92B5D9","VALUE1":"FA817EEC-F930-7069-52DB96A08D828F6B"},
{"ITEMID":9.0,"VALUE3":"FA817EF1-AABB-8AE0-0D1141E449A99A4F","VALUE2":"FA817EF0-0E1C-C0AD-2446C0706A87C9DF","VALUE1":"FA817EEF-0899-5737-A53E353397F401CF"}]

In the mobile client application, I have a SerializationTestController class that handles all of the test logic and communications back and forth with the server. The time for each test is measured from immediately before the the request is made to the server, until after the data has been deserialized to an ArrayCollection. You can view the SerializationTestController class below:

package control
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.getTimer;

	import model.TestSummaryVO;
	import model.TestVO;

	import mx.collections.ArrayCollection;
	import mx.rpc.AsyncToken;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.http.HTTPService;
	import mx.rpc.remoting.RemoteObject;

	import views.SummaryView;

	[Event(name="testStatusChange", type="control.TestUpdateEvent")]
	[Event(name="testUpdate", type="control.TestUpdateEvent")]
	public class SerializationTestController extends EventDispatcher
	{
		private var remoteObject : RemoteObject;
		private var httpService : HTTPService;

		private var _testing : Boolean = false;

		private var testIndex : int = 0;
		private var iterationIndex : int = 0;
		private var recordCountIndex : int = 0;
		private var testInstanceIndex : int = 0;

		private var _results : ArrayCollection;
		private var currentTest : TestVO;

		public static const ITERATIONS : int = 5;
		public static const RECORD_COUNT : Array = [1,10,100,1000,10000];
		public static const TESTS : Array = [ TestVO.TYPE_AMF, TestVO.TYPE_JSON ];

		public function SerializationTestController(target:IEventDispatcher=null)
		{
			super(target);

			remoteObject = new RemoteObject("ColdFusion");
			remoteObject.source = "com.tricedesigns.mobileTest.Services";
			remoteObject.endpoint = "http://tricedesigns.com/flex2gateway/";

			httpService = new HTTPService();

			_results = new ArrayCollection();
		}

		[Bindable(event="testStatusChange")]
		public function get testing ():Boolean
		{
			return _testing;
		}

		public function get results():ArrayCollection
		{
			return _results;
		}

		public function get chartResults() : ArrayCollection
		{
			var result : ArrayCollection = new ArrayCollection();

			for ( var index : int = 0; index < ITERATIONS; index++ ) 			{ 				var summaryVO : TestSummaryVO = new TestSummaryVO(); 				summaryVO.iteration = index+1; 				result.addItem( summaryVO ); 			} 			 			for each ( var vo : TestVO in results ) 			{ 				summaryVO = result.getItemAt( vo.iteration ) as TestSummaryVO; 					 				if ( vo.type == TestVO.TYPE_AMF ) 					summaryVO[ "amfDuration" + SerializationTestController.RECORD_COUNT[ vo.recordIndex ] ] = vo.endTime - vo.startTime; 				else 					summaryVO[ "jsonDuration" + SerializationTestController.RECORD_COUNT[ vo.recordIndex ] ] = vo.endTime - vo.startTime; 				 			} 			 			return result; 		} 		 		 		 		public function startTest() : void 		{ 			if ( _testing )  return; 			 			_testing = true; 			testIndex = 0; 			iterationIndex = 0; 			recordCountIndex = 0; 			testInstanceIndex = 0; 			updateTest(); 			dispatchEvent( new TestUpdateEvent( TestUpdateEvent.TEST_STATUS ) ); 			dispatchEvent( new TestProgressEvent( "STARTING TEST..." ) ); 		} 		 		private function completeTest() : void 		{ 			_testing = false; 			dispatchEvent( new TestUpdateEvent( TestUpdateEvent.TEST_STATUS ) ); 			dispatchEvent( new TestProgressEvent( "TEST COMPLETE" ) ); 		} 		 		private function createTestVO() : void 		{ 			currentTest = new TestVO(); 			currentTest.startTime = getTimer(); 			currentTest.index = testInstanceIndex; 			currentTest.iteration = iterationIndex; 			currentTest.recordIndex = recordCountIndex; 			currentTest.type = TESTS[ testIndex ]; 		} 		 		private function finalizeTestVO(error : Boolean = false) : void 		{ 			if ( error ) 				currentTest.endTime = -1 			else 				currentTest.endTime = getTimer(); 			 			_results.addItem( currentTest ); 			dispatchEvent( new TestUpdateEvent( TestUpdateEvent.TEST_UPDATE,  currentTest ) ); 			dispatchEvent( new TestProgressEvent( "task completed in " + (currentTest.endTime - currentTest.startTime) + " milliseconds" ) ); 			currentTest = null; 		} 		 		private function updateTest() : void 		{ 			if ( iterationIndex >= ITERATIONS )
				return completeTest();

			createTestVO();

			if ( TESTS[ testIndex ] == TestVO.TYPE_AMF )
			{
				doAMFTest();
				recordCountIndex ++;

				if ( recordCountIndex >= RECORD_COUNT.length )
				{
					recordCountIndex = 0;
					testIndex++;
				}
			}

			else if ( TESTS[ testIndex ] == TestVO.TYPE_JSON )
			{
				doJSONTest();
				recordCountIndex ++;

				if ( recordCountIndex >= RECORD_COUNT.length )
				{
					recordCountIndex = 0;
					testIndex = 0;
					iterationIndex ++;
				}
			}

			testInstanceIndex++;
		}

		private function doAMFTest() : void
		{
			dispatchEvent( new TestProgressEvent( "AMF Requesting " + RECORD_COUNT[ recordCountIndex ] ) );
			var token : AsyncToken = remoteObject.getRecords( RECORD_COUNT[ recordCountIndex ] );
			token.addResponder( new mx.rpc.Responder( onAMFResult, onFault ) );
		}

		protected function onAMFResult( event : ResultEvent ) : void
		{
			var result : ArrayCollection = event.result as ArrayCollection;
			finalizeTestVO();
			updateTest();
		}

		private function doJSONTest() : void
		{
			dispatchEvent( new TestProgressEvent( "JSON Requesting " + RECORD_COUNT[ recordCountIndex ] ) );
			httpService.url = "http://tricedesigns.com/com/tricedesigns/mobileTest/Services.cfc?method=getrecords&records=" + RECORD_COUNT[ recordCountIndex ] + "&returnformat=json";
			var token : AsyncToken = httpService.send();
			token.addResponder( new mx.rpc.Responder( onJSONResult, onFault ) );
		}

		protected function onJSONResult( event : ResultEvent ) : void
		{
			var resultString : String = event.result as String;
			var result : ArrayCollection = new ArrayCollection( JSON.parse( resultString ) as Array );

			finalizeTestVO();
			updateTest();

		}

		protected function onFault( event : FaultEvent ) : void
		{
			trace( event.fault.toString() );
			finalizeTestVO(true);
			updateTest();
		}

	}
}

Also, here is the TestVO value object that shows the information captured for each test:

package model
{
	public class TestVO
	{
		public static const TYPE_JSON : String = "json";
		public static const TYPE_AMF : String = "amf";

		public var index : int;
		public var iteration : int = 0;
		public var startTime : int;
		public var endTime : int;
		public var type : String;
		public var recordIndex : int;

		public function TestVO()
		{
		}
	}
}

Summary

Both JSON and AMF are acceptable serialization formats for mobile applications built with AIR.   Both are compact serialization formats that minimize packet size.  Both have native parsing/decoding by the AIR runtime.   AMF will generally provide better performance for larger data sets.  JSON *may* provide marginally better performance for small data sets.  AMF also allows for strongly typed object serialization & deserialization, where JSON does not.

The answer to the question of “should I use AMF or JSON” is subjective… What kind of data are you returning, and how much data is it? Do you already have AMF services built?  Do you already have JSON services built?   Are the services consumed by multiple endpoints, with multiple technologies?  Do you rely upon strongly typed objects in you development and maintenance processes?  Both AMF and JSON are viable solutions for mobile applications.


UPDATE 11/23/2011: Full source code now available at:
https://github.com/triceam/Flex-Mobile-Serialization-Tester


Thanks also to fellow Adobe evangelist Raymond Camden for the CF tips & guidance.

  • Mike McConnell

    Is it correct to assume that, given your description of the technologies, AMF is more secure than JSON? Or would that depend on the protocol through which you access the data? Does AMF eliminate the need to use SSL?

    • http://www.tricedesigns.com Andrew

      No, I wouldn’t say that it is more secure. Both are serialization formats used to structure data, not encryption schemes. Both AMF and JSON are normally delivered over HTTP. HTTP is not considered a secure mechanism. You can add security to both by adding SSL encryption to the HTTP layer (HTTPS).

  • http://esdot.ca shawn

    I think the test is flawed, because 80% of the time must be downloading the file… So, when AMF appears 20% faster, if you remove the download time, it’s actually probably 500% faster.

    I think the real thing we want to measure is code execution time when reading or writing the data.

    10,000 AMF objects shouldn’t take more than 500ms on a mobile device…

    • http://www.tricedesigns.com Andrew

      In these tests I am specifically measuring end-to-end performance, including download time. Not just the client-side deserialization. The user is going to experience the full end-to-end scenario from the point that they request data, to the time that the data is delivered to their device. Using AMF for large data collections can shave off several seconds of download time opposed to JSON.

  • http://esdot.ca shawn

    In my mind that obscures the comparison between the two.

    Download time is not dependant on the format used, except as it relates to fileSize. It’s also highly variable.

    I would much prefer to see a test that measures:
    1. File size between the the two formats (I can calculate theoretical download speed if I want)
    2. Time required to write, and time requires to read, the files into AS3.

    Reading this comparison, I still don’t come away with any idea which produces larger file size, or which is faster to decompress…

    Keep in mind, these formats are not always used to fetch data remotely. Many applications will use AMF or JSON to cache data internally for their applications.

    Knowing which is faster for reading and writing, and therefore more scalable, and less likely to lock up the device, is the key piece of info…

    At the very least, it’s certainly valuable to add another comparison that removes download time from the equation.

    • http://www.tricedesigns.com Andrew

      I’ll see what else I can dig up to get more file size and speed #s out of this. The native JSON parser is significantly faster than the other open source ActionScript based JSON parsers that I have seen.

  • http://esdot.ca shawn

    I’d say AMF is slightly more secure, as in it’s not human readable.

    In order to read AMF, someone would need to capture the file, load it into a flash project, and decompress the data.

    Not secure by any means, but at least they can’t literally open it, and start browsing through the values…

    • http://www.tricedesigns.com Andrew

      I would emphasize the *slightly*, b/c there are cases when you can still read some text out of AMF encoded data when viewed in a text editor. There are also tools to decode AMF. For enterprise class security, I strongly recommend an encryption layer.

  • http://www.tobiasrohde.de Tobias Rohde

    If you use the Firebug-Plugin AMF Explorer (https://addons.mozilla.org/de/firefox/addon/amf-explorer/) AMF is human readable without any problem.

  • http://gamina.org Vic

    If you want performance, would you not use websockets or flash sockets?
    Even iPhone HTML has websockets, why use http when using actinscript

  • http://None Jim

    Have you compared compressed JSON (deflate, gzip)? to AMF? Flash 11 native JSON to as3core?

    • http://www.tricedesigns.com Andrew

      James Ward’s application compares comressed vs uncompressed. I have not played with toggling compression on/off. I do not have hard metrics on native json vs as3core, however my experience has been that it is noticably faster using the native JSON classes.

  • http://www.mikutech.com Mark

    Just to confirm – these tests were run on Flash Player 11, with the native JSON support? You mention it in the article but you don’t explicitly say which version you test against, so I thought I’d make sure.

    • http://www.tricedesigns.com Andrew

      Yes, it uses FP11, using native JSON. I don’t recall the exact version number though.