diff --git a/compiler2/Type.cc b/compiler2/Type.cc index d0c5fe59c3569106d53713565a2a796349b3a025..c8534d6d62c8c13083065fabcc40338981c0e1ea 100644 --- a/compiler2/Type.cc +++ b/compiler2/Type.cc @@ -3013,6 +3013,34 @@ namespace Common { if (NULL != jsonattrib->tag_list) { chk_json_tag_list(); } + + if (jsonattrib->as_map) { + Type* last = get_type_refd_last(); + if (T_SEQOF != last->typetype && T_SETOF != last->typetype) { + error("Invalid attribute, 'as map' requires record of or set of"); + } + else { + Type* of_type = last->get_ofType(); + Type* of_type_last = of_type->get_type_refd_last(); + if ((T_SEQ_T != of_type_last->typetype && + T_SET_T != of_type_last->typetype) || + of_type_last->get_nof_comps() != 2) { + error("Invalid attribute, 'as map' requires the element type to be " + "a record or set with 2 fields"); + } + else { + Type* key_type = of_type_last->get_comp_byIndex(0)->get_type(); + if (key_type->get_type_refd_last()->get_typetype() != T_USTR) { + error("Invalid attribute, 'as map' requires the element type's " + "first field to be a universal charstring"); + } + if (key_type->is_optional_field()) { + error("Invalid attribute, 'as map' requires the element type's " + "first field to be mandatory"); + } + } + } + } } } diff --git a/compiler2/Type_codegen.cc b/compiler2/Type_codegen.cc index 12fd195a4fb2038e1d4bbbd0d60d41b109c9f335..7f55f38c644dffa5e387347ee9ad2f306d8430a4 100644 --- a/compiler2/Type_codegen.cc +++ b/compiler2/Type_codegen.cc @@ -1091,23 +1091,30 @@ void Type::generate_code_jsondescriptor(output_struct *target) target->header.global_vars = mputprintf(target->header.global_vars, "extern const TTCN_JSONdescriptor_t %s_json_;\n", get_genname_own().c_str()); + boolean as_map = (jsonattrib != NULL && jsonattrib->as_map) || + (ownertype == OT_RECORD_OF && parent_type->jsonattrib != NULL && + parent_type->jsonattrib->as_map); + if (NULL == jsonattrib) { target->source.global_vars = mputprintf(target->source.global_vars, - "const TTCN_JSONdescriptor_t %s_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE };\n" - , get_genname_own().c_str()); + "const TTCN_JSONdescriptor_t %s_json_ = { FALSE, NULL, FALSE, NULL, " + "FALSE, FALSE, %s };\n" + , get_genname_own().c_str(), as_map ? "TRUE" : "FALSE"); } else { char* alias = jsonattrib->alias ? mputprintf(NULL, "\"%s\"", jsonattrib->alias) : NULL; char* def_val = jsonattrib->default_value ? mputprintf(NULL, "\"%s\"", jsonattrib->default_value) : NULL; + target->source.global_vars = mputprintf(target->source.global_vars, - "const TTCN_JSONdescriptor_t %s_json_ = { %s, %s, %s, %s, %s, %s };\n" + "const TTCN_JSONdescriptor_t %s_json_ = { %s, %s, %s, %s, %s, %s, %s };\n" , get_genname_own().c_str() , jsonattrib->omit_as_null ? "TRUE" : "FALSE" , alias ? alias : "NULL" , (jsonattrib->as_value || jsonattrib->tag_list != NULL) ? "TRUE" : "FALSE" , def_val ? def_val : "NULL" , jsonattrib->metainfo_unbound ? "TRUE" : "FALSE" - , jsonattrib->as_number ? "TRUE" : "FALSE"); + , jsonattrib->as_number ? "TRUE" : "FALSE" + , as_map ? "TRUE" : "FALSE"); Free(alias); Free(def_val); } @@ -1737,6 +1744,18 @@ void Type::generate_code_Se(output_struct *target) FATAL_ERROR("Type::generate_code_Se()"); // union only, not for record } } + if (jsonattrib != NULL) { + sdef.jsonAsValue = jsonattrib->as_value; + } + if (sdef.nElements == 2) { + Type* first_field_type = get_comp_byIndex(0)->get_type(); + sdef.jsonAsMapPossible = !first_field_type->is_optional_field() && + first_field_type->get_type_refd_last()->typetype == T_USTR; + } + else { + sdef.jsonAsMapPossible = FALSE; + } + sdef.elements = (struct_field*) Malloc(sdef.totalElements*sizeof(*sdef.elements)); memset(sdef.elements, 0, sdef.totalElements * sizeof(*sdef.elements)); diff --git a/compiler2/datatypes.h b/compiler2/datatypes.h index 48ce5ad7734e8e90473c57faa28b4b57834c4f5c..3fe3d6475f2a4ad0a8928eae552849f885f49838 100644 --- a/compiler2/datatypes.h +++ b/compiler2/datatypes.h @@ -121,6 +121,7 @@ typedef struct { boolean xerHasNamespaces; /* from the module */ boolean xerEmbedValuesPossible; /* for sequence */ boolean jsonAsValue; /* for both */ + boolean jsonAsMapPossible; /* for sequence */ /** The index of the last field which can generate empty XML, or -1 */ int exerMaybeEmptyIndex; /* for union */ const char * control_ns_prefix; diff --git a/compiler2/record.c b/compiler2/record.c index 53fc76430fcd6930cb6bc303a21c086e20529884..f13cd53e60fed21a0e138780e2d5984f1d89a922 100644 --- a/compiler2/record.c +++ b/compiler2/record.c @@ -3226,7 +3226,8 @@ char* generate_json_decoder(char* src, const struct_def* sdef) src = mputprintf(src, "int %s::JSON_decode(const TTCN_Typedescriptor_t&%s, JSON_Tokenizer& p_tok, " "boolean p_silent, int)\n" - "{\n", sdef->name, (sdef->nElements == 1 && !sdef->jsonAsValue) ? " p_td" : ""); + "{\n", sdef->name, + ((sdef->nElements == 1 && !sdef->jsonAsValue) || sdef->jsonAsMapPossible) ? " p_td" : ""); if (sdef->nElements == 1) { if (!sdef->jsonAsValue) { @@ -3238,9 +3239,31 @@ char* generate_json_decoder(char* src, const struct_def* sdef) src = mputstr(src, " }\n"); } } + if (!sdef->jsonAsValue) { + src = mputstr(src, " json_token_t j_token = JSON_TOKEN_NONE;\n"); + } + if (sdef->jsonAsMapPossible) { + src = mputprintf(src, + " if (p_td.json->as_map) {\n" + " char* fld_name = NULL;\n" + " size_t name_len = 0;\n" + " size_t buf_pos = p_tok.get_buf_pos();\n" + " size_t dec_len = p_tok.get_next_token(&j_token, &fld_name, &name_len);\n" + " if (JSON_TOKEN_ERROR == j_token) {\n" + " JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_BAD_TOKEN_ERROR, \"\");\n" + " return JSON_ERROR_FATAL;\n" + " }\n" + " else if (JSON_TOKEN_NAME != j_token) {\n" + " p_tok.set_buf_pos(buf_pos);\n" + " return JSON_ERROR_INVALID_TOKEN;\n" + " }\n" + " field_%s.decode_utf8(name_len, (unsigned char*) fld_name);\n" + " return field_%s.JSON_decode(%s_descr_, p_tok, p_silent) + dec_len;\n" + " }\n", sdef->elements[0].name, + sdef->elements[1].name, sdef->elements[1].typedescrname); + } if (!sdef->jsonAsValue) { src = mputstr(src, - " json_token_t j_token = JSON_TOKEN_NONE;\n" " size_t dec_len = p_tok.get_next_token(&j_token, NULL, NULL);\n" " if (JSON_TOKEN_ERROR == j_token) {\n" " JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_BAD_TOKEN_ERROR, \"\");\n" @@ -4738,7 +4761,8 @@ void defRecordClass1(const struct_def *sdef, output_struct *output) " TTCN_EncDec_ErrorContext::error(TTCN_EncDec::ET_UNBOUND,\n" " \"Encoding an unbound value of type %s.\");\n" " return -1;\n" - " }\n\n", name, (sdef->nElements == 1 && !sdef->jsonAsValue) ? " p_td" : "", dispname); + " }\n\n", name, + ((sdef->nElements == 1 && !sdef->jsonAsValue) || sdef->jsonAsMapPossible) ? " p_td" : "", dispname); if (sdef->nElements == 1) { if (!sdef->jsonAsValue) { src = mputstr(src, " if (NULL != p_td.json && p_td.json->as_value) {\n"); @@ -4749,6 +4773,18 @@ void defRecordClass1(const struct_def *sdef, output_struct *output) src = mputstr(src, " }\n"); } } + if (sdef->jsonAsMapPossible) { + src = mputprintf(src, + " if (p_td.json->as_map) {\n" + " TTCN_Buffer key_buf;\n" + " field_%s.encode_utf8(key_buf);\n" + " CHARSTRING key_str;\n" + " key_buf.get_string(key_str);\n" + " return p_tok.put_next_token(JSON_TOKEN_NAME, (const char*) key_str) + \n" + " field_%s.JSON_encode(%s_descr_, p_tok);\n" + " }\n", sdef->elements[0].name, + sdef->elements[1].name, sdef->elements[1].typedescrname); + } if (!sdef->jsonAsValue) { src = mputstr(src, " int enc_len = p_tok.put_next_token(JSON_TOKEN_OBJECT_START, NULL);\n\n"); for (i = 0; i < sdef->nElements; ++i) { diff --git a/compiler2/record_of.c b/compiler2/record_of.c index 0a17e44c0b9602f2a8c8a6d42fdb8ae8d1327081..dfe66158ef276b102c427860513d73cfc1f8dd7c 100644 --- a/compiler2/record_of.c +++ b/compiler2/record_of.c @@ -1545,7 +1545,8 @@ void defRecordOfClass1(const struct_of_def *sdef, output_struct *output) " \"Encoding an unbound value of type %s.\");\n" " return -1;\n" " }\n\n" - " int enc_len = p_tok.put_next_token(JSON_TOKEN_ARRAY_START, NULL);\n" + " int enc_len = p_tok.put_next_token(p_td.json->as_map ? " + "JSON_TOKEN_OBJECT_START : JSON_TOKEN_ARRAY_START, NULL);\n" " for (int i = 0; i < val_ptr->n_elements; ++i) {\n" " if (NULL != p_td.json && p_td.json->metainfo_unbound && !(*this)[i].is_bound()) {\n" // unbound elements are encoded as { "metainfo []" : "unbound" } @@ -1560,7 +1561,8 @@ void defRecordOfClass1(const struct_of_def *sdef, output_struct *output) " enc_len += ret_val;\n" " }\n" " }\n" - " enc_len += p_tok.put_next_token(JSON_TOKEN_ARRAY_END, NULL);\n" + " enc_len += p_tok.put_next_token(p_td.json->as_map ? " + "JSON_TOKEN_OBJECT_END : JSON_TOKEN_ARRAY_END, NULL);\n" " return enc_len;\n" "}\n\n" , name, dispname); @@ -1581,7 +1583,8 @@ void defRecordOfClass1(const struct_of_def *sdef, output_struct *output) " JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_BAD_TOKEN_ERROR, \"\");\n" " return JSON_ERROR_FATAL;\n" " }\n" - " else if (JSON_TOKEN_ARRAY_START != token) {\n" + " else if ((!p_td.json->as_map && JSON_TOKEN_ARRAY_START != token) ||\n" + " (p_td.json->as_map && JSON_TOKEN_OBJECT_START != token)) {\n" " return JSON_ERROR_INVALID_TOKEN;\n" " }\n\n" " set_size(0);\n" @@ -1632,7 +1635,8 @@ void defRecordOfClass1(const struct_of_def *sdef, output_struct *output) " dec_len += (size_t)ret_val2;\n" " }\n\n" " dec_len += p_tok.get_next_token(&token, NULL, NULL);\n" - " if (JSON_TOKEN_ARRAY_END != token) {\n" + " if ((!p_td.json->as_map && JSON_TOKEN_ARRAY_END != token) ||\n" + " (p_td.json->as_map && JSON_TOKEN_OBJECT_END != token)) {\n" " JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_REC_OF_END_TOKEN_ERROR, \"\");\n" " if (p_silent) {\n" " clean_up();\n" diff --git a/compiler2/ttcn3/JsonAST.cc b/compiler2/ttcn3/JsonAST.cc index 098e2f87366a10238a363da66788353354bdde02..249996dca8266cf71b3497194e9345dbeb7251a2 100644 --- a/compiler2/ttcn3/JsonAST.cc +++ b/compiler2/ttcn3/JsonAST.cc @@ -36,6 +36,7 @@ void JsonAST::init_JsonAST() metainfo_unbound = false; as_number = false; tag_list = NULL; + as_map = false; } JsonAST::JsonAST(const JsonAST *other_val) @@ -51,6 +52,7 @@ JsonAST::JsonAST(const JsonAST *other_val) schema_extensions.add(new JsonSchemaExtension(*other_val->schema_extensions[i])); } metainfo_unbound = other_val->metainfo_unbound; + as_map = other_val->as_map; } } @@ -119,4 +121,7 @@ void JsonAST::print_JsonAST() const } } } + if (as_map) { + printf("Encoding elements into a map of key-value pairs.\n\r"); + } } diff --git a/compiler2/ttcn3/JsonAST.hh b/compiler2/ttcn3/JsonAST.hh index 1fbc88bdd716410abbeedaa10da83f3c36230553..46972383816737ea517fc6c39f9b3b252ca7929a 100644 --- a/compiler2/ttcn3/JsonAST.hh +++ b/compiler2/ttcn3/JsonAST.hh @@ -43,6 +43,7 @@ class JsonAST { boolean metainfo_unbound; boolean as_number; rawAST_tag_list* tag_list; + boolean as_map; JsonAST() { init_JsonAST(); } JsonAST(const JsonAST *other_val); diff --git a/compiler2/ttcn3/rawAST.l b/compiler2/ttcn3/rawAST.l index e3886fb7af47a22bdec11e95c45c20c53558c487..5fc6f6b28ac187745f411c778cfabdd0289c511b 100644 --- a/compiler2/ttcn3/rawAST.l +++ b/compiler2/ttcn3/rawAST.l @@ -500,6 +500,7 @@ for RETURN(XKWfor); unbound RETURN(XKWunbound); chosen RETURN(XChosenKeyword); otherwise RETURN(XJsonOtherwise); +map RETURN(XJsonMap); } <INITIAL>{ diff --git a/compiler2/ttcn3/rawAST.y b/compiler2/ttcn3/rawAST.y index edd9be4231b827559bfb2a3df8994af5a0c93a25..ff71b62001420d3b02da4cf61d17c2557a3f1853 100644 --- a/compiler2/ttcn3/rawAST.y +++ b/compiler2/ttcn3/rawAST.y @@ -298,6 +298,7 @@ static void yyprint(FILE *file, int type, const YYSTYPE& value); %token XAsValueKeyword "asValue" %token XChosenKeyword "chosen" %token XJsonOtherwise "otherwise" +%token XJsonMap "map" %type <enumval> @@ -1840,6 +1841,7 @@ JSONattribute: | JMetainfoForUnbound | JAsNumber | JChosen +| JAsMap ; JOmitAsNull: @@ -1880,6 +1882,10 @@ JChosen: } ; +JAsMap: + XKWas XJsonMap { jsonstruct->as_map = true; } +; + %% /* parse_rawAST(), which calls our rawAST_parse, is over in rawASST.l */ diff --git a/core/JSON.cc b/core/JSON.cc index 681eac756d558597488cef557f63e6bb75e63e14..d3295f09ad096a493fcb7dc3f3cfffe10ea07b81 100644 --- a/core/JSON.cc +++ b/core/JSON.cc @@ -32,55 +32,55 @@ #include <sys/types.h> // JSON descriptors for base types -const TTCN_JSONdescriptor_t INTEGER_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t INTEGER_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t FLOAT_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t FLOAT_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t BOOLEAN_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t BOOLEAN_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t BITSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t BITSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t HEXSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t HEXSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t OCTETSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t OCTETSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t CHARSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t CHARSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t UNIVERSAL_CHARSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t UNIVERSAL_CHARSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t VERDICTTYPE_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t VERDICTTYPE_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t GeneralString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t GeneralString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t NumericString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t NumericString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t UTF8String_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t UTF8String_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t PrintableString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t PrintableString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t UniversalString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t UniversalString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t BMPString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t BMPString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t GraphicString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t GraphicString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t IA5String_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t IA5String_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t TeletexString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t TeletexString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t VideotexString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t VideotexString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t VisibleString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t VisibleString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t ASN_NULL_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t ASN_NULL_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t OBJID_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t OBJID_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t ASN_ROID_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t ASN_ROID_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t ASN_ANY_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t ASN_ANY_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; -const TTCN_JSONdescriptor_t ENUMERATED_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE }; +const TTCN_JSONdescriptor_t ENUMERATED_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; diff --git a/core/JSON.hh b/core/JSON.hh index eb40d54f41ee87ee8385d8b598bd49483e35faa9..8506128532c4259d299dd00f80198afaf79cf7ec 100644 --- a/core/JSON.hh +++ b/core/JSON.hh @@ -66,6 +66,13 @@ struct TTCN_JSONdescriptor_t * number, instead of its name form as a JSON string (affects both encoding * and decoding). */ boolean as_number; + + /** If set, encodes the value into a map of key-value pairs (i.e. a fully + * customizable JSON object). The encoded type has to be a record of/set of + * with a record/set element type, which has 2 fields, the first of which is + * a non-optional universal charstring. + * Example: { "key1" : value1, "key2" : value2 } */ + boolean as_map; }; /** This macro makes sure that coding errors will only be displayed if the silent diff --git a/core2/Basetype2.cc b/core2/Basetype2.cc index eabf226ddd041ccdad4cbcadf82ea9bb9bb55a1b..cc0036ccfa8778088dacbaefc1c012d5a7d63919 100644 --- a/core2/Basetype2.cc +++ b/core2/Basetype2.cc @@ -1476,7 +1476,8 @@ int Record_Of_Type::JSON_encode(const TTCN_Typedescriptor_t& p_td, JSON_Tokenize return -1; } - int enc_len = p_tok.put_next_token(JSON_TOKEN_ARRAY_START, NULL); + int enc_len = p_tok.put_next_token(p_td.json->as_map ? JSON_TOKEN_OBJECT_START : + JSON_TOKEN_ARRAY_START, NULL); for (int i = 0; i < get_nof_elements(); ++i) { if (NULL != p_td.json && p_td.json->metainfo_unbound && !get_at(i)->is_bound()) { @@ -1493,7 +1494,8 @@ int Record_Of_Type::JSON_encode(const TTCN_Typedescriptor_t& p_td, JSON_Tokenize } } - enc_len += p_tok.put_next_token(JSON_TOKEN_ARRAY_END, NULL); + enc_len += p_tok.put_next_token(p_td.json->as_map ? JSON_TOKEN_OBJECT_END : + JSON_TOKEN_ARRAY_END, NULL); return enc_len; } @@ -1507,7 +1509,8 @@ int Record_Of_Type::JSON_encode_negtest(const Erroneous_descriptor_t* p_err_desc return -1; } - int enc_len = p_tok.put_next_token(JSON_TOKEN_ARRAY_START, NULL); + int enc_len = p_tok.put_next_token(p_td.json->as_map ? JSON_TOKEN_OBJECT_START : + JSON_TOKEN_ARRAY_START, NULL); int values_idx = 0; int edescr_idx = 0; @@ -1583,7 +1586,8 @@ int Record_Of_Type::JSON_encode_negtest(const Erroneous_descriptor_t* p_err_desc } } - enc_len += p_tok.put_next_token(JSON_TOKEN_ARRAY_END, NULL); + enc_len += p_tok.put_next_token(p_td.json->as_map ? JSON_TOKEN_OBJECT_END : + JSON_TOKEN_ARRAY_END, NULL); return enc_len; } @@ -1601,7 +1605,8 @@ int Record_Of_Type::JSON_decode(const TTCN_Typedescriptor_t& p_td, JSON_Tokenize JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_BAD_TOKEN_ERROR, ""); return JSON_ERROR_FATAL; } - else if (JSON_TOKEN_ARRAY_START != token) { + else if ((!p_td.json->as_map && JSON_TOKEN_ARRAY_START != token) || + (p_td.json->as_map && JSON_TOKEN_OBJECT_START != token)) { return JSON_ERROR_INVALID_TOKEN; } @@ -1661,7 +1666,8 @@ int Record_Of_Type::JSON_decode(const TTCN_Typedescriptor_t& p_td, JSON_Tokenize } dec_len += p_tok.get_next_token(&token, NULL, NULL); - if (JSON_TOKEN_ARRAY_END != token) { + if ((!p_td.json->as_map && JSON_TOKEN_ARRAY_END != token) || + (p_td.json->as_map && JSON_TOKEN_OBJECT_END != token)) { JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_REC_OF_END_TOKEN_ERROR, ""); if (p_silent) { clean_up(); @@ -5951,6 +5957,21 @@ int Record_Type::JSON_encode(const TTCN_Typedescriptor_t& p_td, JSON_Tokenizer& return get_at(0)->JSON_encode(*fld_descr(0), p_tok); } + if (p_td.json->as_map) { // TODO: implement negtest + const UNIVERSAL_CHARSTRING* key_ustr = dynamic_cast< + const UNIVERSAL_CHARSTRING*>(get_at(0)); + if (NULL == key_ustr) { + TTCN_error("Internal error: attribute 'as map' is set, but the first " + "field is not a universal charstring"); + } + TTCN_Buffer key_buf; + key_ustr->encode_utf8(key_buf); + CHARSTRING key_str; + key_buf.get_string(key_str); + return p_tok.put_next_token(JSON_TOKEN_NAME, (const char*) key_str) + + get_at(1)->JSON_encode(*fld_descr(1), p_tok); + } + int enc_len = p_tok.put_next_token(JSON_TOKEN_OBJECT_START, NULL); int field_count = get_count(); @@ -6093,7 +6114,33 @@ int Record_Type::JSON_decode(const TTCN_Typedescriptor_t& p_td, JSON_Tokenizer& // decode that without the need of any brackets or field names return get_at(0)->JSON_decode(*fld_descr(0), p_tok, p_silent); } + json_token_t token = JSON_TOKEN_NONE; + + if (p_td.json->as_map) { + UNIVERSAL_CHARSTRING* key_ustr = dynamic_cast< + UNIVERSAL_CHARSTRING*>(get_at(0)); + if (NULL == key_ustr) { + TTCN_error("Internal error: attribute 'as map' is set, but the first " + "field is not a universal charstring"); + } + char* name = NULL; + size_t name_len = 0; + size_t buf_pos = p_tok.get_buf_pos(); + size_t dec_len = p_tok.get_next_token(&token, &name, &name_len); + if (JSON_TOKEN_ERROR == token) { + JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_BAD_TOKEN_ERROR, ""); + return JSON_ERROR_FATAL; + } + else if (JSON_TOKEN_NAME != token) { + p_tok.set_buf_pos(buf_pos); + return JSON_ERROR_INVALID_TOKEN; + } + key_ustr->decode_utf8(name_len, (unsigned char*) name); + + return get_at(1)->JSON_decode(*fld_descr(1), p_tok, p_silent) + dec_len; + } + size_t dec_len = p_tok.get_next_token(&token, NULL, NULL); if (JSON_TOKEN_ERROR == token) { JSON_ERROR(TTCN_EncDec::ET_INVAL_MSG, JSON_DEC_BAD_TOKEN_ERROR, ""); diff --git a/regression_test/json/AttributeTestcases.ttcn b/regression_test/json/AttributeTestcases.ttcn index fc5b4db7c67905a65c467361ca76c40b6e4a4894..1485c0d6d44a786e6915ae878cbc79d11b302172 100644 --- a/regression_test/json/AttributeTestcases.ttcn +++ b/regression_test/json/AttributeTestcases.ttcn @@ -675,6 +675,20 @@ testcase tc_attribute_chosen_default() runs on MTC { setverdict(pass); } +testcase tc_attribute_map() runs on MTC { + var Map x := { { "one", 1 }, { "two", 2 }, { "three", 3 } }; + var octetstring enc_exp := char2oct("{\"one\":1,\"two\":2,\"three\":3}"); + var octetstring enc := f_enc_map(x); + if (enc != enc_exp) { + setverdict(fail, "Encoding failed. Expected: ", enc_exp, ", got: ", enc); + } + var Map dec := f_dec_map(enc_exp); + if (dec != x) { + setverdict(fail, "Decoding failed. Expected: ", x, ", got: ", dec); + } + setverdict(pass); +} + control { execute(tc_NoAttributeOnUpperLevel()) @@ -704,5 +718,6 @@ control { execute(tc_attribute_chosen()); execute(tc_attribute_chosen_negtest()); execute(tc_attribute_chosen_default()); + execute(tc_attribute_map()); } } diff --git a/regression_test/json/Functions.ttcn b/regression_test/json/Functions.ttcn index 2b626d4d2b7246ac9aca5a7e5c6613786d6e9b43..d877ecd96f3ec2fecab74bbc6f36c7a60299bf1a 100644 --- a/regression_test/json/Functions.ttcn +++ b/regression_test/json/Functions.ttcn @@ -203,6 +203,9 @@ external function f_enc_multi_rec(in MultiLevelRec x) return octetstring external function f_enc_multi_list(in MultiLevelList x) return octetstring with { extension "prototype(convert) encode(JSON)" } +external function f_enc_map(in Map x) return octetstring + with { extension "prototype(convert) encode(JSON)" } + // for ASN.1 types external function f_enc_seqofint(in SeqOfInt x) return octetstring with { extension "prototype(convert) encode(JSON)" } @@ -405,6 +408,9 @@ external function f_dec_multi_rec(in octetstring x) return MultiLevelRec external function f_dec_multi_list(in octetstring x) return MultiLevelList with { extension "prototype(convert) decode(JSON)" } +external function f_dec_map(in octetstring x) return Map + with { extension "prototype(convert) decode(JSON)" } + // for ASN.1 types external function f_dec_seqofint(in octetstring x) return SeqOfInt with { extension "prototype(convert) decode(JSON)" } diff --git a/regression_test/json/Types.ttcn b/regression_test/json/Types.ttcn index 780139c8ab3ac8441e304c64bc3406e49a4e9ce6..15d59c6b4e7ed9acb9cf0f58590f93423eb11676 100644 --- a/regression_test/json/Types.ttcn +++ b/regression_test/json/Types.ttcn @@ -362,6 +362,14 @@ with { variant (protocolId2) "default (0)"; } +type record MapItem { + universal charstring key, + integer value_ +} + +type set of MapItem Map +with { variant "as map" } + } with { encode "JSON"; extension "anytype integer, charstring, R, RoI, Thing";