Projects‎ > ‎USDS‎ > ‎USDS Basic parser‎ > ‎

Examples of Using USDS Basic Parser

Read also: FAQ

At the moment the library supports only one program language: C++
To work with this software, you need "BasicParser32.lib" and headers. You can download these files here: USDS Basic parser
In the archive you will also find the source code for this example.
In the example you will learn, how to create the binary document (client side) and how to parse it (server side).

Client Side

Step 1: Create and Initialize Parser

Parser works with "Scheme" only; for this reason, you have to define the dictionary. One method to do this is to input a text description of the dictionary into the parser:

// Create objects for work
BasicParser* clientParser = new BasicParser();
BinaryOutput* usds_binary_doc = new BinaryOutput();

// Init parser by the dictionary
const char* text_dictionary =
    "USDS DICTIONARY ID=1000000 v.1.0        \
    {                                        \
        
1: STRUCT internalObject             \
        {                                    \
            1: UNSIGNED VARINT varintField;  \
            2: DOUBLE doubleField;         \
            3: STRING<UTF-8> stringField;    \
            4: BOOLEAN booleanField;         \
        } RESTRICT {notRoot;}                \
                                             \
        2: STRUCT rootObject                 \
        {                                    \
            1: INT intField;                 \
            2: LONG longField;               \
            3: ARRAY<internalObject> arrayField;\
        }                                    \
}";

clientParser->addDictionaryFromText(text_dictionary, strlen(text_dictionary), USDS_UTF8);

The full description of the text dictionary can be found here: Specification of the Text Dictionary

The parser can contain several dictionaries - add multiple dictionaries to one text block or execute the method "addDictionaryFromText" several times. You can switch dictionaries rapidly for creating binary documents. When the parser decodes binary documents it selects the dictionary automatically.

Step 2: Create DOM-object

Let's create a DOM-object which will be encoded in the binary document USDS.
Add the first root element "rootObject" and initialize it:
// Add new structure to the DOM object
UsdsStruct* tag = clientParser->addStructTag("rootObject");
// Init simple fields
tag->setFieldValue("intField", 1234);
tag->setFieldValue("longField", 5000000000);

One of the fields is an array of the tag; its size is zero at the moment. Let's initialize it:

// Init array field, get link from the struct
UsdsArray* array_field = tag->getArrayField("arrayField");
for (int i = 0; i < 2; i++)
{
    // Add new element to the array, get link to this element
    UsdsStruct* struct_element = (UsdsStruct*)(array_field->addTagElement());
    // Init element
    struct_element->setFieldValue("varintField", i);
    struct_element->setFieldValue("doubleField", i / 2.0);
    struct_element->setFieldValue("stringField", "String value");
    struct_element->setFieldValue("booleanField", i == 0);
}

Initializing the array of the structures is done by:

  1. Add new empty element to the array using the method "addTagElement";
  2. Initialize the added structure.
In the example above, we used the text names of the fields for initialization. This method takes a lot of processor time, because the parser has to find the fields in the dictionary. The better method is to find integer identifiers for all tags and fields and to use them for initialization. Let's do it:
// Get tag's and fields' IDs from the dictionary
int internalObject_id = clientParser->getTagID("internalObject");
int internalObject_varintField_id = clientParser->getFieldID(internalObject_id, "varintField");
int internalObject_doubleField_id = clientParser->getFieldID(internalObject_id, "doubleField");
int internalObject_stringField_id = clientParser->getFieldID(internalObject_id, "stringField");
int internalObject_booleanField_id = clientParser->getFieldID(internalObject_id, "booleanField");

int rootObject_id = clientParser->getTagID("rootObject");
int rootObject_intField_id = clientParser->getFieldID(rootObject_id, "intField");
int rootObject_longField_id = clientParser->getFieldID(rootObject_id, "longField");
int rootObject_arrayField_id = clientParser->getFieldID(rootObject_id, "arrayField");

// Now use these IDs
// Add second structure to the DOM object
tag = clientParser->addStructTag(rootObject_id);
// Init simple fields
tag->setFieldValue(rootObject_intField_id, 4321);
tag->setFieldValue(rootObject_longField_id, 6000000000);
// Init array field, get link from the struct
array_field = tag->getArrayField(rootObject_arrayField_id);
for (int i = 0; i < 2; i++)
{
    // Add new element to the array, get link to this element
    UsdsStruct* struct_element = (UsdsStruct*)(array_field->addTagElement());
    // Init element
    struct_element->setFieldValue(internalObject_varintField_id, i * 2);
    struct_element->setFieldValue(internalObject_doubleField_id, i / 3.0);
    struct_element->setFieldValue(internalObject_stringField_id, "Second root object");
    struct_element->setFieldValue(internalObject_booleanField_id, i != 0);
}

If you know all integer identifiers beforehand (they are indicated in the text dictionary), you can use them at once.

Step 3: Create JSON

We have two tags in the DOM-object. Let's look at them in JSON format:

// Let's look what we have in DOM-object
std::string json;
clientParser->getJSON(USDS_UTF8, &json);
std::cout << "JSON:\n" << json << "\n";
std::cout << "JSON string size: " << json.size() << " symbols\n\n";

On the screen we will see the following:
JSON:
{
    "rootObject":
    {
        "intField": 1234,
        "longField": 5000000000,
        "arrayField":
        [
            {
                "varintField": 0,
                "doubleField": 0.000000,
                "stringField": "String value",
                "booleanField": true
            },
            {
                "varintField": 1,
                "doubleField": 0.500000,
                "stringField": "String value",
                "booleanField": false
            }
        ]
    },
    "rootObject":
    {
        "intField": 4321,
        "longField": 6000000000,
        "arrayField":
        [
            {
                "varintField": 0,
                "doubleField": 0.000000,
                "stringField": "Second root object",
                "booleanField": false
            },
            {
                "varintField": 2,
                "doubleField": 0.333333,
                "stringField": "Second root object",
                "booleanField": true
            }
        ]
    }
}
JSON string size: 694 symbols

Step 4: Create Binary Document USDS

We have seen that we really have two structures in the DOM-object. Let's create a binary document USDS. It will contain the head, the dictionary and the body:

// Create USDS binary document with Head, Dictionary and Body inside
clientParser->encode(usds_binary_doc, true, true, true);
std::cout << "USDS binary document size: " << usds_binary_doc->getSize() << " bytes\n\n";

On the screen we see the size of the binary document - "USDS binary document size: 286 bytes". The dictionary is about half of the document (140 bytes). If you add to the DOM-object not 2, but 1000 tags, the ratio "dictionary - body" will be better. If the client and the server know the dictionary beforehand, then the client can create a binary document without a dictionary:
clientParser->encode(usds_binary_doc, true, false, true); 

Step 5: Cleaning DOM-object

Let's clear away the DOM-object, but we will not delete the dictionary in the parser, so as not to reinitialize:
clientParser->clearBody(); 

The objects of the classes inside the parser were not destroyed--they will be used again in the next DOM-object (Object Pool). It makes the process faster. All objects will be destroyed when the parser destructor is executed.

The binary document is still available inside the object "usds_binary_doc"; you can get the link to the data array:

const unsigned char* binary_data = usds_binary_doc->getBinary();
size_t binary_size = usds_binary_doc->getSize();

Send the array to the server as you like.

Server Side

Step 1: Create Parser

Let's create the parser on the server side:
// Create object for work
BasicParser* serverParser = new BasicParser();

The dictionary initialization isn't necessary - the parser will get the dictionary from the first USDS binary document (if you added it to the document). The parser stores all unique dictionaries before the method "Clear" is executed.

Step 2: Read the Binary Document

Let's read the binary document:
serverParser->decode(binary_data, binary_size); 

Now the DOM-object is ready. You can look at it in JSON format:

// DOM object created from the binary
// Let's look what we have in it
json.clear();
serverParser->getJSON(USDS_UTF8, &json);
std::cout << "JSON:\n" << json << "\n";
std::cout << "JSON string size: " << json.size() << " symbols\n\n";

On the screen we have the following:

JSON:
{
    "rootObject":
    {
        "intField": 1234,
        "longField": 5000000000,
        "arrayField":
        [
            {
                "varintField": 0,
                "doubleField": 0.000000,
                "stringField": "String value",
                "booleanField": true
            },
            {
                "varintField": 1,
                "doubleField": 0.500000,
                "stringField": "String value",
                "booleanField": false
            }
        ]
    },
    "rootObject":
    {
        "intField": 4321,
        "longField": 6000000000,
        "arrayField":
        [
            {
                "varintField": 0,
                "doubleField": 0.000000,
                "stringField": "Second root object",
                "booleanField": false
            },
            {
                "varintField": 2,
                "doubleField": 0.333333,
                "stringField": "Second root object",
                "booleanField": true
            }
        ]
    }
}
JSON string size: 694 symbols

As we can see, the document is correct. We can now read the DOM-object.

Step 3: Reading Tags and Fields

Let us assume that we do not know the structure of the binary document. Let's check the type and the name of the first element:

Text Box

// Extract first root object
UsdsBaseType* someTag = serverParser->getFirstTag();
std::cout << "The type of the first Tag: '" << someTag->getTypeName() << "'\n";
std::cout << "The name of the first Tag: '" << someTag->getName() << "'\nFields:\n";
if (someTag->getType() != USDS_STRUCT)
    return 1;
else
    tag = (UsdsStruct*)someTag;

On the screen we have the following:
The type of the first Tag: 'STRUCT'
The name of the first Tag: 'rootObject'

This is a good response for our program. We can try to read the fields:

// buffers for the field values
int int_value = 0;
long long long_value = 0;
long long varint_value = 0;
double double_value = 0.0;
const char* string_value = 0;
bool boolean_value = false;

// Extract the fields by the names
tag->getFieldValue("intField", &int_value);
std::cout << "\tintField = " << int_value << "\n";
tag->getFieldValue("longField", &long_value);
std::cout << "\tlongField = " << long_value << "\n";

If the parser cannot convert value from the binary document to your variables, an exception will be thrown (i.e. it is not necessary to check for errors after every step).

Let's read the array:
// Extract array field
array_field = tag->getArrayField("arrayField");
int array_size = array_field->getElementNumber();
for (int i = 0; i < array_size; i++)
{
    // Get the element from the array
    UsdsStruct* struct_element = (UsdsStruct*)(array_field->getTagElement(i));
    struct_element->getFieldValue("varintField", &varint_value);
    std::cout << "\tarrayField[" << i << "].varintField = " << varint_value << "\n";
    struct_element->getFieldValue("doubleField", &double_value);
    std::cout << "\tarrayField[" << i << "].doubleField = " << double_value << "\n";
    struct_element->getFieldValue("stringField", &string_value);
    std::cout << "\tarrayField[" << i << "].stringField = " << string_value << "\n";
    struct_element->getFieldValue("booleanField", &boolean_value);
    std::cout << "\tarrayField[" << i << "].booleanField = " << boolean_value << "\n";
}

We can read all tags and fields using the integer identifiers (it will be faster):

// Get second tag from 
tag = (UsdsStruct*)(tag->getNext());
std::cout << "\nThe name of the second Tag: '" << tag->getName() << "'\nFields:\n";

// Let's optimise performance
// Get tag's and fields' IDs from the dictionary
internalObject_id = serverParser->getTagID("internalObject");
internalObject_varintField_id = serverParser->getFieldID(internalObject_id, "varintField");
internalObject_doubleField_id = serverParser->getFieldID(internalObject_id, "doubleField");
internalObject_stringField_id = serverParser->getFieldID(internalObject_id, "stringField");
internalObject_booleanField_id = serverParser->getFieldID(internalObject_id, "booleanField");

rootObject_id = serverParser->getTagID("rootObject");
rootObject_intField_id = serverParser->getFieldID(rootObject_id, "intField");
rootObject_longField_id = serverParser->getFieldID(rootObject_id, "longField");
rootObject_arrayField_id = serverParser->getFieldID(rootObject_id, "arrayField");

// Extract the fields by the IDs

tag->getFieldValue(rootObject_intField_id, &int_value);
std::cout << "\tintField = " << int_value << "\n";
tag->getFieldValue(rootObject_longField_id, &long_value);
std::cout << "\tlongField = " << long_value << "\n";
// Extract array field
array_field = tag->getArrayField(rootObject_arrayField_id);
array_size = array_field->getElementNumber();
for (int i = 0; i < array_size; i++)
{
    // Get the element from the array
    UsdsStruct* struct_element = (UsdsStruct*)(array_field->getTagElement(i));
    // Init element
    struct_element->getFieldValue(internalObject_varintField_id, &varint_value);
    std::cout << "\tarrayField[" << i << "].varintField = " << varint_value << "\n";
    struct_element->getFieldValue(internalObject_doubleField_id, &double_value);
    std::cout << "\tarrayField[" << i << "].doubleField = " << double_value << "\n";
    struct_element->getFieldValue(internalObject_stringField_id, &string_value);
    std::cout << "\tarrayField[" << i << "].stringField = " << string_value << "\n";
    struct_element->getFieldValue(internalObject_booleanField_id, &boolean_value);
    std::cout << "\tarrayField[" << i << "].booleanField = " << boolean_value << "\n";
}

Step 4: Clear Parser

If you clear only the parser's body, the dictionary can be used for decoding the next binary document (if it has the same dictionary ID). This method works faster.

Text Box

clientParser->clearBody();

Step 5: Destroy All Objects

Execute the following methods for memory cleaning:
delete clientParser;
delete serverParser;
delete usds_binary_doc;

The Object Pool inside the parser will be destroyed too.


2015.11.06, Andrey Abramov
CC BY 4.0

Comments

The gadget spec URL could not be found