From 468b295f31ae536e9f13d3d38a686a565ea9daf4 Mon Sep 17 00:00:00 2001 From: Botond Baranyi <botond.baranyi@ericsson.com> Date: Thu, 11 Oct 2018 15:16:27 +0200 Subject: [PATCH] Implemented JSON attribute 'as map' (bug 540051) Change-Id: I5d228318532b9e6774e77c7604e63e2fba4afab5 Signed-off-by: Botond Baranyi <botond.baranyi@ericsson.com> --- compiler2/Type.cc | 28 ++++++++++ compiler2/Type_codegen.cc | 27 +++++++-- compiler2/datatypes.h | 1 + compiler2/record.c | 42 +++++++++++++- compiler2/record_of.c | 12 ++-- compiler2/ttcn3/JsonAST.cc | 5 ++ compiler2/ttcn3/JsonAST.hh | 1 + compiler2/ttcn3/rawAST.l | 1 + compiler2/ttcn3/rawAST.y | 6 ++ core/JSON.cc | 50 ++++++++--------- core/JSON.hh | 7 +++ core2/Basetype2.cc | 59 ++++++++++++++++++-- regression_test/json/AttributeTestcases.ttcn | 15 +++++ regression_test/json/Functions.ttcn | 6 ++ regression_test/json/Types.ttcn | 8 +++ 15 files changed, 226 insertions(+), 42 deletions(-) diff --git a/compiler2/Type.cc b/compiler2/Type.cc index d0c5fe59c..c8534d6d6 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 12fd195a4..7f55f38c6 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 48ce5ad77..3fe3d6475 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 53fc76430..f13cd53e6 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 0a17e44c0..dfe66158e 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 098e2f873..249996dca 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 1fbc88bdd..469723838 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 e3886fb7a..5fc6f6b28 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 edd9be423..ff71b6200 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 681eac756..d3295f09a 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 eb40d54f4..850612853 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 eabf226dd..cc0036ccf 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 fc5b4db7c..1485c0d6d 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 2b626d4d2..d877ecd96 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 780139c8a..15d59c6b4 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"; -- GitLab