在 C++、PHP、JavaScript 等之间传递参数...






4.25/5 (9投票s)
易于实现,格式简洁,适用于简单的数据交换,尤其适用于新的和不常见的脚本语言之间。
引言
一个常见的编程任务(也许是唯一的任务?)是移动和处理数据,有时是跨语言的。这可能是一项繁重的工作,因为语言的创建者并没有为嵌套数据(如数组)实现任何标准的序列化格式。事实上,许多语言根本不支持任何本地序列化。
如今,脚本语言风靡一时。也许这只是我工作的特定领域,但我似乎接触到的脚本语言越来越多。是的,有那些无处不在的语言,JavaScript、ASP、PHP 等等……但还有无数用于工业机器人控制、相机控制、电话交换等的脚本语言。
在某个时候,您希望将数据导入或导出到这些语言中,而选择可能是模糊的或不理想的。XML 是标准的,但 XML 数据很庞大且难以解析。我真的不想为 ACME 的汉堡翻盖脚本语言添加一个 XML 解析器,这样您就可以将汉堡翻盖统计数据放入您的 Total Meal Performance 包中。超简单的字符串格式不可扩展,并且需要您不断升级一切以处理新参数。我看到的其他自定义格式通常效率低下,用途有限,或者存在错误。
因此,本文的目标是提供一种**易于实现、格式简洁,尤其适用于新的和不常见的脚本语言之间简单数据交换的格式**。
与其称之为 ETIMFFSDEEBNAOSL,听起来像德语,我将正式将这种格式称为**简单跨语言序列化**或**SCS**,这样我们就有了一个简写,以后在设计评审中,我们可以使用诸如“我将把这些数据 SCS 给您”之类的酷短语,让附近的管理人员感到困惑。
目标
不幸的是,上面的部分已经超越了这一部分,不仅提到了目标,还对其开了个玩笑。所以,与其重申,我将提供更多细节。
最重要的是,将强调简单性和灵活性。可以通过一些方式来扩展协议以减小序列化数据的大小,例如使用二进制或压缩等……但取而代之的是会增加编码器/解码器的复杂性,从而增加实现/调试时间,并提高兼容性缺陷和惰性实现的风险。
由于这种数据格式主要用于新的和/或不常见的脚本格式,我们希望找到一种可以快速轻松实现的方案。
因此,要实现我们的目标,步骤将是……
- 为嵌套数据定义一种紧凑的可移植数据格式。
- 概述一种易于实现的编码器/解码器。
- 提供 C++、PHP 和 JavaScript 的示例编码器/解码器。
用途
为了使本文更容易阅读和理解,我将在深入研究之前介绍一些示例用法,因为我没有很棒的图片放在文章顶部。这里有一个简单的 SCS 用法的例子,例如,从 PHP 传递数据到 JavaScript……
// Create PHP array $person_info = array( 'Fred'=>array( 'hair'=>'blonde', 'eye'=>'blue', 'age'=>26 ) ); // Send to Javascript echo 'var javaArray = DeserializeArray( "' . SerializeArray( $person_info ) . '" );';
或者从 PHP 发送数据到 C++ 怎么样?
// PHP - Return data on fred $person_info = QueryDatabase( "fred" ); echo SerializeArray( $person_info ); // C++ - Use fred's age CPsPropertyBag cPersonInfo( GetPhpReturnString() ); long lAge = cPersonInfo[ "Fred" ][ "Age" ].ToLong();
为什么需要另一种格式?
如果您在 Google 上搜索“pass array PHP JavaScript”,替换成您喜欢的语言,您会发现各种实现,包括许多标准的惰性实现,如 XML。这里的目标是定义最简单实用的实现。事实上,理想情况下,惰性实现将不再可能。
**关于 XML 的说明**。我似乎遇到过许多人声称 XML 是终极格式。尽管有数千种格式出现过,但我从未见过如此强烈的“一刀切”的说法。但是,似乎许多人决心让 XML 成为数据交换的唯一格式。甚至有人建议我将流媒体视频数据进行 base-64 编码并将其包装在 XML 中,使其“标准化”。我既不认同,也不会考虑,对任何格式或语言持如此狭隘的观点。格式的功能集与其实现的复杂性之间存在明确的权衡。我将提供我希望在本文范围内对 XML 所面临挑战的客观比较。
**其他标准怎么样?** 有一个巨大的类似协议列表,可以从XML Alternatives 开始。经过数小时的仔细研究,我未能找到一种适合此处解释的目标的方案。如果我遗漏了相同的解决方案,欢迎您大肆宣扬。但我怀疑您能否说这显而易见。在某个时候,只能硬着头皮去完成工作;至少,我会在一个显而易见的地方分享……
**自定义解决方案**。我还要看一个许多人在自定义实现中使用的常见失控编码技术。似乎他们的实现最初是一个平面表示,例如 **a=1,b=2,c=3**,然后添加了递归来处理嵌套数据。虽然简单性很难超越,但嵌套编码产生的数据扩展可能相当显著。它还使得嵌套值难以供人类阅读。由于这两个因素,我将偏离简单性来解决这个问题。
这是一个在 PHP 中的失控实现
function RunawayEncode( &$params, $sep = '&', $arr = '?' ) { $ret = ''; foreach( $params as $key => $val ) { if ( strlen( $key ) && $val ) { // Continue link if ( strlen( $ret ) ) $ret .= $sep; // Multidimensional assignment if ( $arr && is_array( $val ) ) $ret .= $key . '=' . $arr . rawurlencode( MakeParams( $val, $sep, $arr ) ); // Flat assignment else $ret .= $key . '=' . rawurlencode( $val ); } // end if } // end foreach return $ret; } function RunawayDecode( $params, $sep = '&', $arr = '?' ) { $ret = array(); $parr = split( $sep, $params ); foreach( $parr as $val ) { $kv = split( '=', $val ); // NULL param if ( !isset( $kv[ 0 ] ) || !strlen( $kv[ 0 ] ) ); // One dimensional else if ( !isset( $kv[ 1 ] ) ) $ret[ $kv[ 0 ] ] = 1; // Flat assignment else if ( !$arr || $kv[ 1 ][ 0 ] != $arr ) $ret[ $kv[ 0 ] ] = rawurldecode( $kv[ 1 ] ); // Multi dimensional assignment else $ret[ $kv[ 0 ] ] = ParseParams( rawurldecode( substr( $kv[ 1 ], 1 ) ), $sep, $arr ); } return $ret; }
数据格式
SCS 数据编码规则
- 等号字符“=”将分隔名称和值。
- 逗号字符“,”将分隔名称/值对。
- 花括号字符“{”和“}”将包含嵌套数据。
- 所有数据都将使用 RFC 1738 中描述的 URL 编码方案作为字符串进行编码。
很简单吧?我选择 RFC 1738 编码是因为许多脚本语言都有内置函数,如果没有,也很容易实现(请参阅 C++ 的ScsSerialize.h头文件中的示例)。此外,在 URL 中使用此编码的大部分逻辑在这里也适用。这种格式还使我们能够轻松地读取大部分数据。下面是一个编码数组的示例。
Mary{Married=No,DOB=7-2-82}
上述数组的 PHP 声明将是
$test_array = array( 'Mary'=> array( 'Married'=>'No', 'DOB'=>'7-2-82', ), );
这是一个稍微复杂一点的示例,演示了嵌套编码。请注意数据只编码了一次。
Mary{Married=No,DOB=7-2-82,Pets{Dog=1},Invalid%20Characters=%21%40%23%24%25%5E%26%2A%28%29}
在 PHP 中声明……
$test_array = array( 'Mary'=> array( 'Married'=>'No', 'DOB'=>'7-2-82', 'Pets'=> array( 'Dog'=>1, ), 'Invalid Characters'=>'!@#$%^&*()' ), );
解析
如前所述,这里最大的优点是易于解析。这是一个 PHP 实现的例子。您会注意到编码函数与上面失控的示例复杂性相似;但是,解码函数更复杂。这种额外的复杂性避免了数据的重复编码。我和其他人尝试了一个更简单的解码器但失败了。我很想知道是否有人做得更好。
function ScsSerialize( &$params ) { $ret = ''; foreach( $params as $key => $val ) { if ( $ret ) $ret .= ','; // Save the key $ret .= rawurlencode( $key ); // Save array if ( is_array( $val ) ) $ret .= '{' . ScsSerialize( $val ) . '}'; // Save single value if any else if ( strlen( $val ) ) $ret .= '=' . rawurlencode( $val ); } // end foreach return $ret; } function ScsDeserialize( $params, &$last = 0 ) { $arr = array(); $l = strlen( $params ); $s = 0; $e = 0; while ( $e < $l ) { switch( $params[ $e ] ) { case ',' : case '}' : { // Any data here? if ( 1 < $e - $s ) { // Divide $a = split( '=', substr( $params, $s, $e - $s ) ); // Valid? if ( !isset( $a[ 0 ] ) ) $a[ 0 ] = 0; else $a[ 0 ] = rawurldecode( $a[ 0 ] ); // Single value? if ( !isset( $a[ 1 ] ) ) $arr[ $a[ 0 ] ] = ''; // Key / value pair else $arr[ $a[ 0 ] ] = rawurldecode( $a[ 1 ] ); } // end if // Move start $s = $e + 1; // Punt if end of array if ( '}' == $params[ $e ] ) { if ( $last ) $last = $e + 1; return $arr; } } break; case '{' : { $k = rawurldecode( substr( $params, $s, $e - $s ) ); if ( isset( $k ) ) { $end_array = 1; $arr[ $k ] = ScsDeserialize( substr( $params, $e + 1 ), $end_array ); $e += $end_array; } // end if $s = $e + 1; } break; } // end switch // Next e $e++; } // end while return $arr; }
这是 JavaScript 版本。并不完全一对一,因为 JavaScript 似乎不支持通用类型的引用。
function ScsSerialize( x_params )
{
var ret = '';
for ( var key in x_params )
{
if ( key && x_params[ key ] )
{
// Continue link
if ( ret ) ret += ',';
// Save the key
ret += escape( key );
if( x_params[ key ].constructor == Array ||
x_params[ key ].constructor == Object )
{
ret += '{' + ScsSerialize( x_params[ key ] ) + '}';
}
else ret += '=' + escape( x_params[ key ] );
} // end if
}
return ret;
}
function ScsDeserialize( x_params, x_arr )
{
var l = x_params.length, s = 0, e = 0;
while ( e < l )
{
switch( x_params[ e ] )
{
case ',' : case '}' :
{
var a = x_params.substr( s, e - s ).split( '=' );
if ( 1 < e - s )
{
// Valid?
if ( null == a[ 0 ] ) a[ 0 ] = 0;
// Decode
else a[ 0 ] = unescape( a[ 0 ] );
// Single value?
if ( null == a[ 1 ] ) x_arr[ 0 ] = '';
// Key / value pair
else x_arr[ a[ 0 ] ] = unescape( a[ 1 ] );
} // end if
// Next data
s = e + 1;
// Punt if end of array
if ( '}' == x_params[ e ] ) return e + 1;
} break;
case '{' :
{
// Get the key
var k = x_params.substr( s, e - s );
if ( k.length )
{
// Decode the key
k = unescape( k );
// Decode array
x_arr[ k ] = Array();
e += ScsDeserialize( x_params.substr( e ), x_arr[ k ] );
} // end if
// Next data
s = e + 1;
} break;
} // end switch
// Next e
e++;
} // end while
return e;
}
我知道它有点长,但我还是会发布 C++ 版本,以便使这篇文章尽可能完整。那些疯狂复制粘贴的人肯定会喜欢它。实际的编码/解码函数差不多,但我添加了将字符串转换为整数和双精度等的功能……只是为了方便使用。C++ 没有此类型的内置支持。
#include <map> #include <string> //================================================================== // TScsPropertyBag // /// Implements a multi-dimensional property bag with nested serialization /** This class provides functionality for a multi-dimensional property bag. It also provides automatic type conversions and, hopefully, easily ported serialization. Typical use CScsPropertyBag arr1, arr2; arr1[ "A" ][ "AA" ] = "Hello World!"; arr1[ "A" ][ "AB" ] = (long)1; arr1[ "B" ][ "BA" ] = (double)3.14159; for ( long i = 0; i < 4; i++ ) arr1[ "list" ][ i ] = i * 2; // Encode CScsPropertyBag::t_String str = arr.serialize(); // Let's have a look at the encoded string... TRACE( str.c_str() ); TRACE( _T( "\n" ) ); // Decode arr2.deserialize( str ); // 'Hello World!' check... TRACE( arr2[ "A" ][ "AA" ] ); TRACE( _T( "\n" ) ); // Get long value long lVal = arr2[ "A" ][ "AB" ].ToLong(); // Get double double dVal = arr2[ "B" ][ "BA" ].ToDouble(); // Get string value LPCTSTR pString = arr2[ "list" ][ 0 ]; */ //================================================================== template < class T > class TScsPropertyBag { public: //================================================================== // CAutoMem // /// Just a simple auto pointer /** This class is a simple auto pointer. It has properties that I particularly like for this type of job. I'll quit making my own when boost comes with VC... */ //================================================================== template < class T > class CAutoMem { public: /// Default constructor CAutoMem() { m_p = NULL; } /// Destructor ~CAutoMem() { release(); } /// Release allocated object void release() { if ( m_p ) { delete m_p; m_p = NULL; } } /// Returns a pointer to encapsulated object T& Obj() { if ( !m_p ) m_p = new T; return *m_p; } /// Returns a pointer to the encapsulated object operator T&() { return Obj(); } private: /// Contains a pointer to the controlled object T *m_p; }; /// Unicode friendly string typedef std::basic_string< T > t_String; /// Our multi-dimensional string array type typedef std::map< t_String, CAutoMem< TScsPropertyBag< T > > > t_StringArray; public: /// Default constructor TScsPropertyBag() { } //============================================================== // TScsPropertyBag() //============================================================== /// Constructos object from encoded string /** \param [in] sStr - Encoded array */ TScsPropertyBag( t_String sStr ) { deserialize( sStr ); } //============================================================== // IsStdChar() //============================================================== /// Returns non-zero if the character does *not* need encoding /** \param [in] ch - Character to check */ static bool IsStdChar( T ch ) { return ( _T( 'a' ) <= ch && _T( 'z' ) >= ch ) || ( _T( 'A' ) <= ch && _T( 'Z' ) >= ch ) || ( _T( '0' ) <= ch && _T( '9' ) >= ch ) || _T( '_' ) == ch || _T( '-' ) == ch || _T( '.' ) == ch; } //============================================================== // urlencode() //============================================================== /// Returns URL encoded version of a string. /** \param [in] sStr - String to encode \return TSend_string object containing encoded string */ static t_String urlencode( t_String sStr ) { t_String sRes; long lLen = sStr.length(), i = 0; T tmp[ 256 ]; while ( i < lLen ) { if ( IsStdChar( sStr[ i ] ) ) sRes += sStr[ i ]; else { _stprintf( tmp, _T( "%%%02lX" ), (long)sStr[ i ] ); sRes += tmp; } // end else i++; } // end while return sRes; } //============================================================== // urldecode() //============================================================== /// Decodes URL encoded string /** \param [in] sStr - URL encoded string to decode \return Decoded string */ static t_String urldecode( t_String sStr ) { t_String sRes; long lLen = sStr.length(), i = 0; T tmp[ 256 ]; while ( i < lLen ) { if ( _T( '%' ) != sStr[ i ] ) sRes += sStr[ i ]; else { tmp[ 0 ] = sStr[ ++i ]; tmp[ 1 ] = sStr[ ++i ]; tmp[ 2 ] = 0; sRes += (TCHAR)( _tcstoul( tmp, NULL, 16 ) ); } // end else i++; } // end while return sRes; } //============================================================== // destroy() //============================================================== /// Releases all memory resources and prepares class for reuse. void destroy() { m_lstSub.clear(); m_str.release(); } //============================================================== // serialize() //============================================================== /// Serializes the array /** \return Serialized array. \see */ t_String serialize() { t_String sRes; // Just return our value if we're not an array if ( !IsArray() ) return m_str.Obj(); // Iterator t_StringArray::iterator pos = m_lstSub.begin(); // For each array element while ( pos != m_lstSub.end() ) { // Add separator if needed if ( sRes.length() ) sRes += _T( ',' ); sRes += pos->first; // Is it an array? if ( pos->second.Obj().IsArray() ) { sRes += _T( '{' ); sRes += pos->second.Obj().serialize(); sRes += _T( '}' ); } // Serialize the value else sRes += _T( '=' ), sRes += urlencode( (LPCTSTR)pos->second.Obj() ); // Next array element pos++; } // end while return sRes; } //============================================================== // deserialize() //============================================================== /// Deserializes an array from string /** \param [in] sStr - Serialized array string. \param [in] bMerge - Non-zero if array should be merged into current data. Set to zero to replace current array. \param [in] pLast - Receives the number of bytes decoded. \param [in] pPs - Property bag that receives any decoded characters. We could also have just called this function on the object, but this way provides a little extra flexibility for later. \return Number of items deserialized. \see */ LONG deserialize( t_String sStr, BOOL bMerge = FALSE, LONG *pLast = NULL, TScsPropertyBag *pPs = NULL ) { // Ensure object if ( !pPs ) pPs = this; // Do we want to merge? if ( !bMerge ) pPs->destroy(); LONG lItems = 0; long lLen = sStr.length(), s = 0, e = 0; while ( e < lLen ) { switch( sStr[ e ] ) { case ',' : case '}' : { if ( 1 < e - s ) { // Find '=' long a = s; while ( a < e && '=' != sStr[ a ] ) a++; t_String sKey, sVal; // First character is separator if ( a == s ) sKey = urldecode( t_String( &sStr.c_str()[ s + 1 ], e - s - 1 ) ); else sKey = urldecode( t_String( &sStr.c_str()[ s ], a - s ) ); // Single token if ( 1 >= e - a ) (*pPs)[ sKey ] = _T( "" ); // Both tokens present else (*pPs)[ sKey ] = urldecode( t_String( &sStr.c_str()[ a + 1 ], e - a - 1 ) ); // Count one item lItems++; } // end if // Next element s = e + 1; // Time to exit? if ( '}' == sStr[ e ] ) { if ( pLast ) *pLast = e + 1; return lItems; } } break; case '{' : { // Get key t_String sKey = urldecode( t_String( &sStr.c_str()[ s ], e - s ) ); // Do we have a key? if ( sKey.length() ) { // This will point to the end of the array we're about to decode LONG lEnd = 0; // Get the sub array lItems += deserialize( t_String( &sStr.c_str()[ e + 1 ] ), TRUE, &lEnd, &(*pPs)[ sKey ] ); // Skip the array we just decoded e += lEnd; } // end if // Skip this token s = e + 1; } break; } // end switch // Next i e++; } // end while return lItems; } //============================================================== // operator []() //============================================================== /// Indexes into sub array /** \param [in] pKey - Index key \return Reference to sub class. \see */ TScsPropertyBag& operator []( LPCTSTR pKey ) { return m_lstSub[ pKey ]; } //============================================================== // operator []() //============================================================== /// Indexes into sub array /** \param [in] sKey - Index key \return Reference to sub class. \see */ TScsPropertyBag& operator []( t_String sKey ) { return m_lstSub[ sKey.c_str() ]; } //============================================================== // operator []() //============================================================== /// Indexes into sub array /** \param [in] n - Index key \return Reference to sub class. \see */ TScsPropertyBag& operator []( long n ) { TCHAR szKey[ 256 ] = _T( "" ); _stprintf( szKey, _T( "%li" ), n ); return m_lstSub[ szKey ]; } //============================================================== // operator []() //============================================================== /// Indexes into sub array /** \param [in] n - Index key \return Reference to sub class. \see */ TScsPropertyBag& operator []( unsigned long n ) { TCHAR szKey[ 256 ] = _T( "" ); _stprintf( szKey, _T( "%lu" ), n ); return m_lstSub[ szKey ]; } //============================================================== // operator []() //============================================================== /// Indexes into sub array /** \param [in] n - Index key \return Reference to sub class. \see */ TScsPropertyBag& operator []( double n ) { TCHAR szKey[ 256 ] = _T( "" ); _stprintf( szKey, _T( "%g" ), n ); return m_lstSub[ szKey ]; } //============================================================== // operator = () //============================================================== /// Conversion from string object t_String operator = ( t_String sStr ) { m_str.Obj() = sStr.c_str(); return m_str.Obj(); } //============================================================== // operator = () //============================================================== /// Conversion from string t_String operator = ( LPCTSTR pStr ) { m_str.Obj() = pStr; return m_str.Obj(); } //============================================================== // operator = () //============================================================== /// Conversion from long t_String operator = ( long lVal ) { T num[ 256 ] = _T( "" ); _stprintf( num, _T( "%li" ), lVal ); m_str.Obj() = num; return m_str.Obj(); } //============================================================== // operator = () //============================================================== /// Conversion from unsigned long t_String operator = ( unsigned long ulVal ) { T num[ 256 ] = _T( "" ); _stprintf( num, _T( "%lu" ), ulVal ); m_str.Obj() = num; return m_str.Obj(); } //============================================================== // operator = () //============================================================== /// Conversion from double t_String operator = ( double dVal ) { T num[ 256 ] = _T( "" ); _stprintf( num, _T( "%g" ), dVal ); m_str.Obj() = num; return m_str.Obj(); } //============================================================== // LPCTSTR() //============================================================== /// Conversion to string operator LPCTSTR() { return ToStr(); } //============================================================== // ToStr() //============================================================== /// Returns local string object LPCTSTR ToStr() { return m_str.Obj().c_str(); } //============================================================== // ToLong() //============================================================== /// Converts to long long ToLong() { return _tcstol( ToStr(), NULL, 10 ); } //============================================================== // ToULong() //============================================================== /// Converts to unsigned long long ToULong() { return _tcstoul( ToStr(), NULL, 10 ); } //============================================================== // ToDouble() //============================================================== /// Converts to double long ToDouble() { return _tcstod( ToStr(), NULL ); } //============================================================== // IsArray() //============================================================== /// Returns non-zero if array elements are present BOOL IsArray() { return 0 < m_lstSub.size(); } private: /// Our value CAutoMem< t_String > m_str; /// Array of strings t_StringArray m_lstSub; }; /// Property bag type /** \see TScsPropertyBag */ typedef TScsPropertyBag< TCHAR > CScsPropertyBag;
比较
在简单性方面,很难再简单了。我见过的唯一更简单的版本是失控类型或特定于语言的。例如,手动输出 JavaScript 数组。在这种情况下,如果我们想切换到其他目标语言,我们的工作就白费了。
XML 擅长的一点是人类可读性,如下所示。虽然可以解析 SCS 字符串,但除非添加换行符,否则它并不清晰。本可以使解码器不区分大小写,但这需要对数据进行分词。这仅仅是有人可能在实现中省略的东西,因此我们就偏离了目标。此外,引入空白字符在将数据作为字符串粘贴到源文件中时可能会引起问题。在这里,为了更接近我们的跨语言通信目标,这具有优先权。虽然我们尝试使其具有一定的可读性,但请注意,在考虑选项时,人类可读性不是 SCS 的优先事项。
在带宽方面,比如 AJAX 项目。考虑以下数组……
// Test array
$A = array( 'Department'=>
array(
'Accounting'=>
array(
'John'=>
array(
'Married'=>'Yes',
'DOB'=>'1-14-78',
'Pets'=>
array(
'Fish'=>8,
'Dog'=>1,
'Cat'=>2
),
'ValidCharacters'=>'.-_',
'InvalidCharacters'=>'[,=]'
),
'Mary'=>
array(
'Married'=>'No',
'DOB'=>'7-2-82',
'Pets'=>
array(
'Dog'=>1,
),
'InvalidCharacters'=>'!@#$%^&*()'
),
),
),
);
我们的 SCS 实现为 **218** 字节。比 XML 等效项少 42%。但更难阅读。它看起来像这样
Department{Accounting{John{Married=Yes,DOB=1-14-78,Pets{Fish=8,Dog=1,Cat=2},\
ValidCharacters=.-_,InvalidCharacters=%5B%2C%3D%5D},\
Mary{Married=No,DOB=7-2-82,Pets{Dog=1},\
InvalidCharacters=%21%40%23%24%25%5E%26%2A%28%29}}}
这个典型的 XML 输出重达 **517** 字节。我有点纠结是否要删除头部和格式字符。我决定保留它们,因为这实际上是使用 XML 的很大一部分理由,即“标准化”。这实际上是作弊,因为我使用了相同的 URL 编码而不是更常见的 base-64。但是,XML 允许我这样做。
<?xml version="1.0" encoding="UTF-8" ?>
<Department>
<Accounting>
<John>
<Married>Yes</Married>
<DOB>1-14-78</DOB>
<Pets>
<Fish>8</Fish>
<Dog>1</Dog>
<Cat>2</Cat>
</Pets>
<ValidCharacters>.-_</ValidCharacters>
<InvalidCharacters>%5B%2C%3D%5D</InvalidCharacters>
</John>
<Mary>
<Married>No</Married>
<DOB>7-2-82</DOB>
<Pets>
<Dog>1</Dog>
</Pets>
<InvalidCharacters>%21%40%23%24%25%5E%26%2A%28%29</InvalidCharacters>
</Mary>
</Accounting>
</Department>
典型的失控实现。应注意的是,此示例特别放大了冗余编码问题。在其他情况下,它可能会有竞争力,但从未显着更好。请注意由于递归编码导致的严重损坏。
Department=?Accounting%3D%3FJohn%253D%253FMarried%25253DYes%252526DOB%25253D1-14-78%252526\
Pets%25253D%25253FFish%2525253D8%25252526Dog%2525253D1%25252526Cat%2525253D2%252526\
ValidCharacters%25253D.-_%252526InvalidCharacters%25253D%2525255B%2525252C%2525253D%\
2525255D%2526Mary%253D%253FMarried%25253DNo%252526DOB%25253D7-2-82%252526\
Pets%25253D%25253FDog%2525253D1%252526InvalidCharacters%25253D%25252521%25252540\
%25252523%25252524%25252525%2525255E%25252526%2525252A%25252528%25252529
灵活性
我们不会尝试在解析器级别编码变量类型或其他属性,如最小值和最大值。但这些事情仍然可以在当前协议的框架内完成。例如,考虑以下 XML
<variable name=x type=float min=-10 max=10>3.14</variable>
我们可以通过添加一个子数组来表示这类信息。在 XML 的情况下,内容或值字段隐含在标签之间。我们将需要添加一个显式的“value”字段。结果实际上比最小的 XML 要短。
variable{name=x,type=float,min=-10,max=10,value=3.14}
或者更好……
x{type=float,min=-10,max=10,value=3.14}
此外,在给定范围内不能有相似的名称。例如……
<table><tr><td>One</td><td>Two</td></tr>
<tr><td>Three</td></tr><table>
将不得不表示为类似
table{tr{0{td{0=One,1=Two}},1{td{0=three}}}}
// For clarity
table
{
tr
{
0
{
td
{
0 = One,
1 = Two
}
},
1
{
td
{
0 = three
}
}
}
}
您会发现大多数数据结构都能很好地在此协议中表示。这通常只是效率问题,尤其是在处理高带宽、二进制数据(如实时视频或音频)时。再说,有什么格式能涵盖一切呢?
结论
我认为这提供了一个关于尝试过什么以及实现了什么的好想法。一些说明……
请注意,提供的函数允许您轻松地序列化数组的部分以及整个数组。此外,您可以将一个数组解码到另一个更大的数组中。这是一个微妙但强大的结构。
在 C++ 实现中实现的属性包概念是该语言的一项强大补充。它可以极大地缩短处理数据时的开发时间。C++ 的好处在于您可以准确地描述运算符的行为方式。我实际上使用了一个更高级的此类,它允许序列化/反序列化到许多格式,如 Windows 注册表、INI 文件、URL GET 和 POST 变量、MIME 格式、数据库等……这是一种处理通用数据的极其强大的方法。我知道我不是第一个发明这个的,有很多例子……
我想在这个例子中添加更多语言。Perl、Python、VB 都在考虑之中。如果有人愿意捐赠,请随时提出。
感谢大家!