title |
---|
Apache Celix Properties Encoding |
In Apache Celix, properties represent key-value pairs, often used for configuration. While these properties are not JSON objects inherently, they can be encoded to and decoded from JSON for interoperability or storage. This page explains how Apache Celix properties are encoded to and decoded from JSON.
Except for empty arrays and the double values NaN, Infinity, and -Infinity, all Apache Celix properties types can be encoded to JSON.
The reason for the empty array limitation is that for a properties array entry the array list element type is must be known, this is not possible to infer from an empty JSON array. To ensure that everything this is encoded, can be decoded again, a properties array entry with an empty array is not encoded to JSON.
The reason for the double values NaN, Infinity, and -Infinity limitation is that JSON does not support these values.
When decoding JSON to Apache Celix properties, the following limitations apply:
- Mixed array types are not supported. For example, an array with both strings and longs cannot be decoded to a properties' entry.
- null values are not supported, because properties does not support a null value type.
- Empty arrays are not supported, because the array list element type must be known, this is not possible to infer from an empty JSON array.
- JSON keys that collide on the created properties' key level are not supported. See [Properties Decoding](##Properties Decoding) for more information.
Apache Celix properties can be encoded to JSON using the celix_properties_save
, celix_properties_saveToStream
and celix_properties_saveToString
functions. These functions take a properties object and encode it to a JSON object
string. The encoding can be controlled using flags and can be done in a flat or nested structure.
By default, the encoding is done in a flat structure, because a flat structure ensures that all keys of the properties can be represented in JSON format. When properties are encoded to JSON in a flat structure, the reverse operation, decoding JSON that has been encoded from properties, will result in the same properties (except for the previously mentioned limitations (empty arrays and the double values NaN, Infinity, and -Infinity)).
Flat Encoding example:
#include <stdio.h>
#include <celix/properties.h>
int main() {
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "single/strKey", "strValue");
celix_properties_setLong(props, "single/longKey", 42);
celix_properties_setDouble(props, "single/doubleKey", 2.0);
celix_properties_setBool(props, "single/boolKey", true);
celix_properties_assignVersion(props, "single/versionKey", celix_version_create(1, 2, 3, "qualifier"));
celix_array_list_t* strArr = celix_arrayList_createStringArray();
celix_arrayList_addString(strArr, "value1");
celix_arrayList_addString(strArr, "value2");
celix_properties_assignArrayList(props, "array/stringArr", strArr);
celix_array_list_t* longArr = celix_arrayList_createLongArray();
celix_arrayList_addLong(longArr, 1);
celix_arrayList_addLong(longArr, 2);
celix_properties_assignArrayList(props, "array/longArr", longArr);
celix_array_list_t* doubleArr = celix_arrayList_createDoubleArray();
celix_arrayList_addDouble(doubleArr, 1.0);
celix_arrayList_addDouble(doubleArr, 2.0);
celix_properties_assignArrayList(props, "array/doubleArr", doubleArr);
celix_array_list_t* boolArr = celix_arrayList_createBoolArray();
celix_arrayList_addBool(boolArr, true);
celix_arrayList_addBool(boolArr, false);
celix_properties_assignArrayList(props, "array/boolArr", boolArr);
celix_array_list_t* versionArr = celix_arrayList_createVersionArray();
celix_arrayList_assignVersion(versionArr, celix_version_create(1, 2, 3, "qualifier"));
celix_arrayList_assignVersion(versionArr, celix_version_create(4, 5, 6, "qualifier"));
celix_properties_assignArrayList(props, "array/versionArr", versionArr);
celix_properties_saveToStream(props, stdout, CELIX_PROPERTIES_ENCODE_PRETTY);
}
Will output the following JSON (order of keys can differ):
{
"array/doubleArr": [
1.0,
2.0
],
"array/boolArr": [
true,
false
],
"single/versionKey": "version<1.2.3.qualifier>",
"array/longArr": [
1,
2
],
"single/strKey": "strValue",
"single/doubleKey": 2.0,
"single/boolKey": true,
"array/versionArr": [
"version<1.2.3.qualifier>",
"version<4.5.6.qualifier>"
],
"array/stringArr": [
"value1",
"value2"
],
"single/longKey": 42
}
When properties are encoded to JSON in a nested structure, the keys of the properties are used to create a nested JSON
object. This is done by using the '/' character in the properties key to create a nested JSON objects. When encoding
properties using a nested structure, there is a risk of key collisions. To detect key collisions, the flag
CELIX_PROPERTIES_ENCODE_ERROR_ON_COLLISIONS
can be used.
Nested Encoding example:
#include <stdio.h>
#include <celix/properties.h>
int main() {
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "single/strKey", "strValue");
celix_properties_setLong(props, "single/longKey", 42);
celix_properties_setDouble(props, "single/doubleKey", 2.0);
celix_properties_setBool(props, "single/boolKey", true);
celix_properties_assignVersion(props, "single/versionKey", celix_version_create(1, 2, 3, "qualifier"));
celix_array_list_t* strArr = celix_arrayList_createStringArray();
celix_arrayList_addString(strArr, "value1");
celix_arrayList_addString(strArr, "value2");
celix_properties_assignArrayList(props, "array/stringArr", strArr);
celix_array_list_t* longArr = celix_arrayList_createLongArray();
celix_arrayList_addLong(longArr, 1);
celix_arrayList_addLong(longArr, 2);
celix_properties_assignArrayList(props, "array/longArr", longArr);
celix_array_list_t* doubleArr = celix_arrayList_createDoubleArray();
celix_arrayList_addDouble(doubleArr, 1.0);
celix_arrayList_addDouble(doubleArr, 2.0);
celix_properties_assignArrayList(props, "array/doubleArr", doubleArr);
celix_array_list_t* boolArr = celix_arrayList_createBoolArray();
celix_arrayList_addBool(boolArr, true);
celix_arrayList_addBool(boolArr, false);
celix_properties_assignArrayList(props, "array/boolArr", boolArr);
celix_array_list_t* versionArr = celix_arrayList_createVersionArray();
celix_arrayList_assignVersion(versionArr, celix_version_create(1, 2, 3, "qualifier"));
celix_arrayList_assignVersion(versionArr, celix_version_create(4, 5, 6, "qualifier"));
celix_properties_assignArrayList(props, "array/versionArr", versionArr);
celix_properties_saveToStream(props, stdout, CELIX_PROPERTIES_ENCODE_PRETTY | CELIX_PROPERTIES_ENCODE_NESTED_STYLE);
}
Will output the following JSON (order of keys can differ):
{
"array": {
"doubleArr": [
1.0,
2.0
],
"boolArr": [
true,
false
],
"longArr": [
1,
2
],
"versionArr": [
"version<1.2.3.qualifier>",
"version<4.5.6.qualifier>"
],
"stringArr": [
"value1",
"value2"
]
},
"single": {
"versionKey": "version<1.2.3.qualifier>",
"strKey": "strValue",
"doubleKey": 2.0,
"boolKey": true,
"longKey": 42
}
}
Properties encoding flags can be used control the behavior of the encoding. The following encoding flags can be used:
-
CELIX_PROPERTIES_ENCODE_PRETTY
: Flag to indicate that the encoded output should be pretty; e.g. encoded with additional whitespaces, newlines and indentation. If this flag is not set, the encoded output will compact; e.g. without additional whitespaces, newlines and indentation. -
CELIX_PROPERTIES_ENCODE_FLAT_STYLE
: Flag to indicate that the encoded output should be flat; e.g. all properties entries are written as top level field entries. -
CELIX_PROPERTIES_ENCODE_NESTED_STYLE
: Flag to indicate that the encoded output should be nested; e.g. properties entries are split on '/' and nested in JSON objects. -
CELIX_PROPERTIES_ENCODE_ERROR_ON_COLLISIONS
: Flag to indicate that the encoding should fail if the JSON representation will contain colliding keys. Note that colliding keys can only occur when using the nested encoding style. -
CELIX_PROPERTIES_ENCODE_ERROR_ON_EMPTY_ARRAYS
: Flag to indicate that the encoding should fail if the JSON representation will contain empty arrays. -
CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF
: Flag to indicate that the encoding should fail if the JSON representation will contain NaN or Inf values. -
CELIX_PROPERTIES_ENCODE_STRICT
: Flag to indicate that all encode "error on" flags should be set.
JSON can be decoded to an Apache Celix properties object using
the celix_properties_load
, celix_properties_loadFromStream
and celix_properties_loadFromString
functions. These
functions take a JSON input and decode it to a properties object. Because properties use a flat key structure,
decoding a nested JSON object to properties results in combining JSON object keys to a flat key structure. This can
result in key collisions.
By default, the decoding will not fail on empty arrays, null values, empty keys, or mixed arrays and instead these JSON entries will be ignored. Also by default, if decoding results in a duplicate properties key, the last value will be used and no error will be returned.
Given a example.json
file with the following content:
{
"counters": {
"counter1": 1,
"counter2": 2
},
"strings": {
"string1": "value1",
"string2": "value2"
}
}
Combined with the following code:
#include <stdio.h>
#include <celix/properties.h>
int main() {
celix_autoptr(celix_properties_t) props;
celix_status_t status = celix_properties_load("example.json", 0, &props):
(void)status; //for production code check status
CELIX_PROPERTIES_ITERATE(props, iter) {
printf("key=%s, value=%s\n", celix_properties_key(iter.key), celix_properties_value(iter.entry.value));
}
}
Will output the following:
key=counters/counter1, value=1
key=counters/counter2, value=2
key=strings/string1, value=value1
key=strings/string2, value=value2
Properties decoding behavior can be controlled using flags. The following decoding flags can be used:
-
CELIX_PROPERTIES_DECODE_ERROR_ON_DUPLICATES
: Flag to indicate that the decoding should fail if the input contains duplicate JSON keys. -
CELIX_PROPERTIES_DECODE_ERROR_ON_COLLISIONS
: Flag to indicate that the decoding should fail if the input contains entry that collide on property keys. -
CELIX_PROPERTIES_DECODE_ERROR_ON_NULL_VALUES
: Flag to indicate that the decoding should fail if the input contains null values. -
CELIX_PROPERTIES_DECODE_ERROR_ON_EMPTY_ARRAYS
: Flag to indicate that the decoding should fail if the input contains empty arrays. -
CELIX_PROPERTIES_DECODE_ERROR_ON_EMPTY_KEYS
: Flag to indicate that the decoding should fail if the input contains empty keys. -
CELIX_PROPERTIES_DECODE_ERROR_ON_MIXED_ARRAYS
: Flag to indicate that the decoding should fail if the input contains mixed arrays. -
CELIX_PROPERTIES_DECODE_STRICT
: Flag to indicate that the decoding should fail if the input contains any of the decode error flags.