Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add CBA_fnc_parseJSON and CBA_fnc_encodeJSON ports from ALiVE #1269

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions addons/hashes/CfgFunctions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class CfgFunctions {
PATHTO_FNC(hashSize);
PATHTO_FNC(isHash);
PATHTO_FNC(parseYAML);
PATHTO_FNC(encodeJSON);
PATHTO_FNC(parseJSON);
PATHTO_FNC(serializeNamespace);
PATHTO_FNC(deserializeNamespace);
};
Expand Down
89 changes: 89 additions & 0 deletions addons/hashes/fnc_encodeJSON.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include "script_component.hpp"
/* ----------------------------------------------------------------------------
Function: CBA_fnc_encodeJSON

Description:
Encodes any SQF value to a JSON string.

Parameters:
_data - SQF data <ANY>.

Returns:
JSON string. <STRING>

Author:
Naught, commy2
---------------------------------------------------------------------------- */
if (isNil "_this") exitWith {"undefined"}; // Return

switch (typeName _this) do
{
case "SCALAR": {format["%1", _this]}; // Return
case "BOOL": {format["%1", _this]}; // Return

case "OBJECT":
{
if (isNull _this) exitWith {"null"}; // Return

'{' + // Return object format
'"type": "object",' +
'"netId": ' + (netId _this) +
'}'
};

case "GROUP":
{
if (isNull _this) exitWith {"null"}; // Return

'{' + // Return object format
'"type": "group",' +
'"netId": ' + (netId _this) +
'}'
};

case "STRING":
{
private ["_output"];
_strArr = toArray(_this);

if (22 in _strArr) then // Escape double quotes (UTF-8 &#22)
{
private ["_output", "_offset"];
_output = [22];
_offset = 1;

{ // forEach
if (_x in [22,92]) then
{
_output set [(_forEachIndex + _offset), 92]; // Escape with backslash (UTF-8 &#92)
_offset = _offset + 1;
};

_output set [(_forEachIndex + _offset), _x];
} forEach toArray(_this);

_output pushback 22;
toString(_output) // Return
}
else
{
("""" + _this + """") // Return
};
};

case "ARRAY":
{
private ["_output"];
_output = "[";

{ // forEach
if (_forEachIndex > 0) then {_output = _output + ","};
_output = _output + (_x call CBA_fnc_encodeJSON);
} forEach _this;

(_output + "]") // Return
};

// For all other types, just convert to string
default {str(_this) call CBA_fnc_encodeJSON};
};
222 changes: 222 additions & 0 deletions addons/hashes/fnc_parseJSON.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#define DEBUG_MODE_FULL
#include "script_component.hpp"
/* ----------------------------------------------------------------------------
Function: CBA_fnc_parseJSON

Description:
Converts a JSON formatted string into a CBA Hash array.

Parameters:
_file - The JSON string. <STRING>

Returns:
Hash data structure taken from the file, or nil if file had syntax errors.

Examples:
(begin example)
_hash = _file call CBA_fnc_parseJSON;
(end)

Author:
Tupolov, commy2
---------------------------------------------------------------------------- */
#define JSON_MODE_VALUE 0
#define JSON_MODE_KEY 1

#define JSON_TYPE_ARRAY 2
#define JSON_TYPE_STRING 3
#define JSON_TYPE_OBJECT 4

// JSON specific tokens
#define JSON_OBJECT_START 123
#define JSON_OBJECT_FINISH 125
#define JSON_ARRAY_START 91
#define JSON_ARRAY_FINISH 93

// JSON terminators - colon for end of key, comma for end of value.
#define ASCII_COLON 58
#define ASCII_COMMA 44
// ----------------------------------------------------------------------------

private _fnc_parse = {
// Accepts array of characters as ASCII codes.
// Returns Hash | Array values and pos.
scopeName "main";
private _return = [];
#define RETURN (_return breakOut "main")
#define LOOP for "_" from 0 to 1 step 0 do

params ["_charArray", "_pos", "_mode", "_type"];
TRACE_1("Parse",toString _charArray);

private _key = "";
private _value = nil;

if (isNil {_charArray select _pos}) exitWith {
ERROR_2("charArray too small. pos = %1, charArray = %2",_pos,_charArray);
false
};

private ["_tempHash", "_tempArray"];
TRACE_2("Starting at",_pos,_charArray select _pos);
switch (_type) do {
case JSON_TYPE_OBJECT: {
_tempHash = [] call CBA_fnc_hashCreate;
TRACE_1("Creating hash",_tempHash);
};

case JSON_TYPE_ARRAY: {
_tempArray = [];
};
};

LOOP {
private _continue = true;

switch (_mode) do {
case JSON_MODE_KEY: {
private _tempKey = [];

while {_continue} do {
private _char = _charArray select _pos;

if (isNil "_char") exitWith {
ERROR_2("Parsing JSON. charArray = %1 and pos = %2",_charArray,_pos);
_continue = false;
RETURN;
};

if (_char == ASCII_COLON) then {
// End of key.
TRACE_1("Setting Key",toString _tempKey);
_key = toString _tempKey call CBA_fnc_trim;

// Remove quotes.
if (_key select [0,1] == """" && _key select [count _key - 1] == """") then {
_key = _key select [1, count _key - 2];
};

_mode = JSON_MODE_VALUE;
_continue = false;
} else {
_tempKey pushBack _char;
};

_pos = _pos + 1;
};
};

case JSON_MODE_VALUE: {
private _tempValue = [];

while {_continue} do {
private _char = _charArray select _pos;

if (isNil "_char") exitWith {
ERROR_2("Parsing JSON. charArray = %1 and pos = %2",_charArray,_pos);
_continue = false;
RETURN;
};

switch (_char) do {
case JSON_OBJECT_START: {
_pos = _pos + 1;
TRACE_1("Starting hash",_pos);

private _temp = [_charArray, _pos, JSON_MODE_KEY, JSON_TYPE_OBJECT] call _fnc_parse;
_value = _temp select 0;
_pos = _temp select 1;
TRACE_1("Got hash",_value);

_mode = JSON_MODE_VALUE;
_continue = false;
};

case JSON_OBJECT_FINISH: {
[_tempHash, _key, _value] call CBA_fnc_hashSet;
TRACE_1("Finishing hash",_tempHash);

_return = [_tempHash, _pos];
_continue = false;
RETURN;
};

case JSON_ARRAY_START: {
_pos = _pos + 1;
TRACE_1("Starting array",_pos);

private _temp = [_charArray, _pos, JSON_MODE_VALUE, JSON_TYPE_ARRAY] call _fnc_parse;
_value = _temp select 0;
_pos = _temp select 1;

_mode = JSON_MODE_VALUE;
_continue = false;
};

case JSON_ARRAY_FINISH: {
if (isNil "_value") then {
_tempArray = [];
} else {
_tempArray pushBack _value;
};
TRACE_1("Finishing array",_tempArray);

_return = [_tempArray, _pos];
_continue = false;
RETURN;
};

case ASCII_COMMA: {
// Means end of value.
switch (_type) do {
case JSON_TYPE_OBJECT: {
TRACE_2("Setting hash",_key,_value);
[_tempHash, _key, _value] call CBA_fnc_hashSet;

_mode = JSON_MODE_KEY;
_continue = false;
};

case JSON_TYPE_ARRAY: {
TRACE_2("Setting array",_tempArray,_value);
_tempArray pushBack _value;
_tempValue = [];
};
};
};

default {
_tempValue pushBack _char;
_value = toString _tempValue call CBA_fnc_trim;

// Remove quotes.
if (_value select [0,1] == """" && _value select [count _value - 1] == """") then {
_value = _value select [1, count _value - 2];
};
//parseSimpleArray format ["[%1]", _thing] select 0

if (_value == "any") then {
_value = "nil";
};
};
};

_pos = _pos + 1;
};
};
};
};
};

// MAIN
params [["_file", "", [""]]];
_file = loadFile _file;

if (toUpper _file in ["", " ", "ERROR", "UNAUTHORISED!"]) exitWith {"ERROR"};

// Create an array of characters from the JSON string.
private _charArray = toArray _file;

// Set position to start with (skip " character) and start parsing.
private _pos = 1;
[_charArray, _pos, JSON_MODE_KEY, JSON_TYPE_OBJECT] call _fnc_parse select 0 // return
2 changes: 0 additions & 2 deletions addons/hashes/fnc_parseYAML.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ Returns:
Author:
Spooner
---------------------------------------------------------------------------- */
SCRIPT(parseYAML);

#include "\x\cba\addons\strings\script_strings.hpp"

#define YAML_MODE_STRING 0
Expand Down
2 changes: 1 addition & 1 deletion addons/hashes/test.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#define DEBUG_MODE_FULL
#include "script_component.hpp"

#define TESTS ["hashEachPair", "hashes", "parseYaml", "hashFilter"]
#define TESTS ["hashEachPair", "hashes", "parseYaml", "parseJSON", "hashFilter"]

SCRIPT(test-hashes);

Expand Down
32 changes: 32 additions & 0 deletions addons/hashes/test_parseJSON.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// ----------------------------------------------------------------------------

#include "script_component.hpp"

SCRIPT(test_parseJSON);

// ----------------------------------------------------------------------------

private ["_expected", "_result", "_fn", "_data"];

_fn = "CBA_fnc_parseJSON";
LOG("Testing " + _fn);

TEST_DEFINED("CBA_fnc_parseJSON",_fn);

/*_data = ["\x\cba\addons\hashes\test_parseJSON_config.json"] call CBA_fnc_parseJSON;

TEST_TRUE([_data] call CBA_fnc_isHash,_fn);

_result = [_data, "anotherNumber"] call CBA_fnc_hashGet;
_expected = "42";
TEST_OP(_result,==,_expected,_fn);

_result = ([_data, "nestedArray"] call CBA_fnc_hashGet) select 0;
_expected = "first";
TEST_OP(_result,==,_expected,_fn);

_result = (([_data, "nestedArray"] call CBA_fnc_hashGet) select 2) select 1;
_expected = "3.2";
TEST_OP(_result,==,_expected,_fn);

nil
17 changes: 17 additions & 0 deletions addons/hashes/test_parseJSON_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"firstName": "Jason",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021"
},
"phoneNumber": [
{ "type": "home", "number": "212 555-1234" },
{ "type": "fax", "number": "646 555-4567" }
],
"newSubscription": false,
"companyName": null
}
Loading