diff --git a/compiler2/Type.cc b/compiler2/Type.cc index c8534d6d62c8c13083065fabcc40338981c0e1ea..cebbec554629a9634833ac279baf787cb8759046 100644 --- a/compiler2/Type.cc +++ b/compiler2/Type.cc @@ -3004,10 +3004,15 @@ namespace Common { "record of, set of, array or field of a record or set"); } } - if (jsonattrib->as_number && - get_type_refd_last()->get_typetype_ttcn3() != T_ENUM_T) { - error("Invalid attribute, 'as number' is only allowed for enumerated " - "types"); + if (jsonattrib->as_number) { + if (get_type_refd_last()->get_typetype_ttcn3() != T_ENUM_T) { + error("Invalid attribute, 'as number' is only allowed for enumerated " + "types"); + } + else if (0 != jsonattrib->enum_texts.size()) { + warning("Attribute 'text ... as ...' will be ignored, because the " + "enumerated values are encoded as numbers"); + } } if (NULL != jsonattrib->tag_list) { @@ -3041,6 +3046,35 @@ namespace Common { } } } + + if (0 != jsonattrib->enum_texts.size()) { + Type* last = get_type_refd_last(); + if (T_ENUM_T != last->get_typetype_ttcn3()) { + error("Invalid attribute, 'text ... as ...' requires an enumerated " + "type"); + } + else { + for (size_t i = 0; i < jsonattrib->enum_texts.size(); ++i) { + Identifier id(Identifier::ID_TTCN, + string(jsonattrib->enum_texts[i]->from), true); + if (!last->has_ei_withName(id)) { + error("Attribute 'text ... as ...' refers to invalid enumerated " + "value '%s'", jsonattrib->enum_texts[i]->from); + } + else { + jsonattrib->enum_texts[i]->index = static_cast<int>( + last->get_eis_index_byName(id)); + for (size_t j = 0; j < i; ++j) { + if (jsonattrib->enum_texts[j]->index == + jsonattrib->enum_texts[i]->index) { + error("Duplicate attribute 'text ... as ...' for enumerated " + "value '%s'", jsonattrib->enum_texts[i]->from); + } + } + } + } + } + } } } @@ -7631,7 +7665,11 @@ namespace Common { { Type *t = this; while (true) { - if (t->has_encoding(CT_JSON)) return t->get_genname_own(my_scope); + if ((t->jsonattrib != NULL && !t->jsonattrib->empty()) || + (t->ownertype == OT_RECORD_OF && t->parent_type->jsonattrib != NULL && + t->parent_type->jsonattrib->as_map)) { + return t->get_genname_own(my_scope); + } else if (t->is_ref()) t = t->get_type_refd(); else break; } diff --git a/compiler2/Type_codegen.cc b/compiler2/Type_codegen.cc index 7f55f38c644dffa5e387347ee9ad2f306d8430a4..7c2381d02fa6cb2740c5c6566f596615b3225c29 100644 --- a/compiler2/Type_codegen.cc +++ b/compiler2/Type_codegen.cc @@ -1098,15 +1098,33 @@ void Type::generate_code_jsondescriptor(output_struct *target) if (NULL == jsonattrib) { target->source.global_vars = mputprintf(target->source.global_vars, "const TTCN_JSONdescriptor_t %s_json_ = { FALSE, NULL, FALSE, NULL, " - "FALSE, FALSE, %s };\n" + "FALSE, FALSE, %s, 0, NULL };\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; + char* enum_texts_name; + if (0 != jsonattrib->enum_texts.size()) { + enum_texts_name = mprintf("%s_json_enum_texts", get_genname_own().c_str()); + target->source.global_vars = mputprintf(target->source.global_vars, + "const JsonEnumText %s[] = { ", enum_texts_name); + for (size_t i = 0; i < jsonattrib->enum_texts.size(); ++i) { + target->source.global_vars = mputprintf(target->source.global_vars, + "%s{ %d, \"%s\" }", i == 0 ? "" : ", ", + jsonattrib->enum_texts[i]->index, jsonattrib->enum_texts[i]->to); + } + target->source.global_vars = mputstr(target->source.global_vars, + " };\n"); + } + else { + enum_texts_name = mcopystr("NULL"); + } + target->source.global_vars = mputprintf(target->source.global_vars, - "const TTCN_JSONdescriptor_t %s_json_ = { %s, %s, %s, %s, %s, %s, %s };\n" + "const TTCN_JSONdescriptor_t %s_json_ = { %s, %s, %s, %s, %s, %s, %s, " + "%d, %s };\n" , get_genname_own().c_str() , jsonattrib->omit_as_null ? "TRUE" : "FALSE" , alias ? alias : "NULL" @@ -1114,9 +1132,12 @@ void Type::generate_code_jsondescriptor(output_struct *target) , def_val ? def_val : "NULL" , jsonattrib->metainfo_unbound ? "TRUE" : "FALSE" , jsonattrib->as_number ? "TRUE" : "FALSE" - , as_map ? "TRUE" : "FALSE"); + , as_map ? "TRUE" : "FALSE" + , static_cast<int>(jsonattrib->enum_texts.size()) + , enum_texts_name); Free(alias); Free(def_val); + Free(enum_texts_name); } } @@ -1418,7 +1439,7 @@ void Type::generate_code_Choice(output_struct *target) case T_SEQOF: case T_SETOF: case T_ARRAY: - sdef.elements[i].jsonValueType = JSON_ARRAY; + sdef.elements[i].jsonValueType = JSON_ARRAY | JSON_OBJECT; break; default: FATAL_ERROR("Type::generate_code_Choice - invalid field type %d", tt); diff --git a/compiler2/enum.c b/compiler2/enum.c index 0036af9e79291eb25445db06d285e20fc11e3c6e..e4d6193a154f8c774494d1440d56016ec3a167e6 100644 --- a/compiler2/enum.c +++ b/compiler2/enum.c @@ -802,8 +802,23 @@ void defEnumClass(const enum_def *edef, output_struct *output) " \"Encoding an unbound value of enumerated type %s.\");\n" " return -1;\n" " }\n\n" - " char* tmp_str = p_td.json->as_number ? mprintf(\"%%d\", enum_value) : " - "mprintf(\"\\\"%%s\\\"\", enum_to_str(enum_value));\n" + " char* tmp_str;\n" + " if (p_td.json->as_number) {" + " tmp_str = mprintf(\"%%d\", enum_value);\n" + " }\n" + " else {\n" + " boolean text_found = false;\n" + " for (size_t i = 0; i < p_td.json->nof_enum_texts; ++i) {\n" + " if (p_td.json->enum_texts[i].index == enum_value) {\n" + " tmp_str = mprintf(\"\\\"%%s\\\"\", p_td.json->enum_texts[i].text);\n" + " text_found = true;\n" + " break;\n" + " }\n" + " }\n" + " if (!text_found) {\n" + " tmp_str = mprintf(\"\\\"%%s\\\"\", enum_to_str(enum_value));\n" + " }\n" + " }\n" " int enc_len = p_tok.put_next_token(p_td.json->as_number ? " "JSON_TOKEN_NUMBER : JSON_TOKEN_STRING, tmp_str);\n" " Free(tmp_str);\n" @@ -835,7 +850,17 @@ void defEnumClass(const enum_def *edef, output_struct *output) " else if ((JSON_TOKEN_STRING == token && !p_td.json->as_number) || use_default) {\n" " if (use_default || (value_len > 2 && value[0] == '\\\"' && value[value_len - 1] == '\\\"')) {\n" " if (!use_default) value[value_len - 1] = 0;\n" - " enum_value = str_to_enum(value + (use_default ? 0 : 1));\n" + " boolean text_found = false;\n" + " for (size_t i = 0; i < p_td.json->nof_enum_texts; ++i) {\n" + " if (strcmp(p_td.json->enum_texts[i].text, value + (use_default ? 0 : 1)) == 0) {\n" + " enum_value = static_cast<%s>(p_td.json->enum_texts[i].index);\n" + " text_found = true;\n" + " break;\n" + " }\n" + " }\n" + " if (!text_found) {\n" + " enum_value = str_to_enum(value + (use_default ? 0 : 1));\n" + " }\n" " if (!use_default) value[value_len - 1] = '\\\"';\n" " if (%s == enum_value) {\n" " error = TRUE;\n" @@ -871,7 +896,7 @@ void defEnumClass(const enum_def *edef, output_struct *output) " }\n" " return (int)dec_len;\n" "}\n\n" - , name, unknown_value, enum_type, unbound_value, unbound_value); + , name, enum_type, unknown_value, enum_type, unbound_value, unbound_value); } if (oer_needed) { diff --git a/compiler2/ttcn3/JsonAST.cc b/compiler2/ttcn3/JsonAST.cc index 249996dca8266cf71b3497194e9345dbeb7251a2..71eb8e1462e5e6146f74041b3dd6c37317c0d4e8 100644 --- a/compiler2/ttcn3/JsonAST.cc +++ b/compiler2/ttcn3/JsonAST.cc @@ -27,6 +27,12 @@ JsonSchemaExtension::~JsonSchemaExtension() Free(value); } +JsonEnumText::~JsonEnumText() +{ + Free(from); + Free(to); +} + void JsonAST::init_JsonAST() { omit_as_null = false; @@ -53,6 +59,10 @@ JsonAST::JsonAST(const JsonAST *other_val) } metainfo_unbound = other_val->metainfo_unbound; as_map = other_val->as_map; + for (size_t i = 0; i < other_val->enum_texts.size(); ++i) { + enum_texts.add(new JsonEnumText(mcopystr(other_val->enum_texts[i]->from), + mcopystr(other_val->enum_texts[i]->to))); + } } } @@ -68,6 +78,17 @@ JsonAST::~JsonAST() free_rawAST_tag_list(tag_list); delete tag_list; } + for (size_t i = 0; i < enum_texts.size(); ++i) { + delete enum_texts[i]; + } + enum_texts.clear(); +} + +boolean JsonAST::empty() const +{ + return omit_as_null == false && alias == NULL && as_value == false && + default_value == NULL && metainfo_unbound == false && as_number == false && + tag_list == NULL && as_map == false && enum_texts.size() == 0; } void JsonAST::print_JsonAST() const @@ -124,4 +145,11 @@ void JsonAST::print_JsonAST() const if (as_map) { printf("Encoding elements into a map of key-value pairs.\n\r"); } + if (0 != enum_texts.size()) { + printf("Enum texts:"); + for (size_t i = 0; i < enum_texts.size(); ++i) { + printf(" '%s' -> '%s'", enum_texts[i]->from, enum_texts[i]->to); + } + printf("\n\r"); + } } diff --git a/compiler2/ttcn3/JsonAST.hh b/compiler2/ttcn3/JsonAST.hh index 46972383816737ea517fc6c39f9b3b252ca7929a..5669ed0b93596a8e5c5e1a7d390e758f2a6b7270 100644 --- a/compiler2/ttcn3/JsonAST.hh +++ b/compiler2/ttcn3/JsonAST.hh @@ -29,6 +29,14 @@ public: ~JsonSchemaExtension(); }; +struct JsonEnumText { + char* from; + char* to; + int index; // set during semantic analysis + JsonEnumText(char* p_from, char* p_to): from(p_from), to(p_to) {} + ~JsonEnumText(); +}; + class JsonAST { private: void init_JsonAST(); @@ -44,11 +52,14 @@ class JsonAST { boolean as_number; rawAST_tag_list* tag_list; boolean as_map; + vector<JsonEnumText> enum_texts; JsonAST() { init_JsonAST(); } JsonAST(const JsonAST *other_val); ~JsonAST(); + boolean empty() const; + void print_JsonAST() const; }; diff --git a/compiler2/ttcn3/rawAST.y b/compiler2/ttcn3/rawAST.y index ff71b62001420d3b02da4cf61d17c2557a3f1853..c6c657a440ae02caf1cfd13a0664b91b52b087ba 100644 --- a/compiler2/ttcn3/rawAST.y +++ b/compiler2/ttcn3/rawAST.y @@ -1505,9 +1505,49 @@ XERattribute: } | text { - xerstruct->text_ = (NamespaceSpecification *)Realloc(xerstruct->text_, - ++xerstruct->num_text_ * sizeof(NamespaceSpecification)); - xerstruct->text_[xerstruct->num_text_-1] = $1; + if (selected_codec == Common::Type::CT_XER || legacy_codec_handling) { + xerstruct->text_ = (NamespaceSpecification *)Realloc(xerstruct->text_, + ++xerstruct->num_text_ * sizeof(NamespaceSpecification)); + xerstruct->text_[xerstruct->num_text_-1] = $1; + } + if (selected_codec != Common::Type::CT_XER) { + XerAttributes::NameChange special; + special.nn_ = $1.prefix; + switch (special.kw_) { + case NamespaceSpecification::NO_MANGLING: + case NamespaceSpecification::CAPITALIZED: + case NamespaceSpecification::UNCAPITALIZED: + case NamespaceSpecification::UPPERCASED: + case NamespaceSpecification::LOWERCASED: + case NamespaceSpecification::ALL: + if (!legacy_codec_handling && + selected_codec == Common::Type::CT_JSON) { + Common::Location loc(infile, @$); + loc.error("This format is not supported for the JSON codec"); + } + break; + default: // it's a real string + if (selected_codec == Common::Type::CT_JSON || + legacy_codec_handling) { + if (legacy_codec_handling) { + // in this case the strings are saved in both the XML and JSON + // structs, so we can't use the same strings + jsonstruct->enum_texts.add( + new JsonEnumText(mcopystr($1.prefix), mcopystr($1.uri))); + } + else { + jsonstruct->enum_texts.add( + new JsonEnumText($1.prefix, $1.uri)); + } + json_f = true; + } + else { + Free($1.prefix); + Free($1.uri); + } + break; + } + } } | XKWuntagged { xerstruct->untagged_ = true; } | XKWuseNil { xerstruct->useNil_ = true; } diff --git a/core/Charstring.cc b/core/Charstring.cc index 3599d764d08b4ecc9fe1ec7a7d50d4dbf2abaddd..4286b245f74dc2d4a14b5df90365289ba4cb65f2 100644 --- a/core/Charstring.cc +++ b/core/Charstring.cc @@ -1405,7 +1405,8 @@ int CHARSTRING::XER_decode(const XERdescriptor_t& p_td, XmlReaderWrap& reader, depth = reader.Depth(); } else if ((depth != -1 || omit_tag) - && (XML_READER_TYPE_TEXT == type || XML_READER_TYPE_CDATA == type || (omit_tag && XML_READER_TYPE_ATTRIBUTE))) + && (XML_READER_TYPE_TEXT == type || XML_READER_TYPE_CDATA == type || + (omit_tag && XML_READER_TYPE_ATTRIBUTE == type))) // Process #text node if we already processed the element node, or // there is no element node because UNTAGGED is in effect. { diff --git a/core/JSON.cc b/core/JSON.cc index d3295f09ad096a493fcb7dc3f3cfffe10ea07b81..2e0e854eb8c6453ef9bdbd9bb224581b60cd95d1 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, FALSE }; +const TTCN_JSONdescriptor_t INTEGER_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t FLOAT_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t FLOAT_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t BOOLEAN_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t BOOLEAN_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t BITSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t BITSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t HEXSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t HEXSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t OCTETSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t OCTETSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t CHARSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t CHARSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t UNIVERSAL_CHARSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t UNIVERSAL_CHARSTRING_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t VERDICTTYPE_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t VERDICTTYPE_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t GeneralString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t GeneralString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t NumericString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t NumericString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t UTF8String_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t UTF8String_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t PrintableString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t PrintableString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t UniversalString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t UniversalString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t BMPString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t BMPString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t GraphicString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t GraphicString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t IA5String_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t IA5String_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t TeletexString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t TeletexString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t VideotexString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t VideotexString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t VisibleString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t VisibleString_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t ASN_NULL_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t ASN_NULL_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t OBJID_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t OBJID_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t ASN_ROID_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t ASN_ROID_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t ASN_ANY_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t ASN_ANY_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; -const TTCN_JSONdescriptor_t ENUMERATED_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE }; +const TTCN_JSONdescriptor_t ENUMERATED_json_ = { FALSE, NULL, FALSE, NULL, FALSE, FALSE, FALSE, 0, NULL }; diff --git a/core/JSON.hh b/core/JSON.hh index 8506128532c4259d299dd00f80198afaf79cf7ec..6cd88edd05d657f581850ce1b92b6089cbb6cb9a 100644 --- a/core/JSON.hh +++ b/core/JSON.hh @@ -21,6 +21,12 @@ class JSON_Tokenizer; class CHARSTRING; class INTEGER; +/** Enumerated text change structure */ +struct JsonEnumText { + int index; + const char* text; +}; + /** Descriptor for JSON encoding/decoding during runtime */ struct TTCN_JSONdescriptor_t { @@ -73,6 +79,12 @@ struct TTCN_JSONdescriptor_t * a non-optional universal charstring. * Example: { "key1" : value1, "key2" : value2 } */ boolean as_map; + + /** Number of enumerated values whose texts are changed. */ + size_t nof_enum_texts; + + /** List of enumerated values whose texts are changed. */ + const JsonEnumText* enum_texts; }; /** 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 9777189db02f28c9c08a163e0a40d22c7aa5a8c8..fc4b0996c97908f76a919632450d9af2ae00486a 100644 --- a/core2/Basetype2.cc +++ b/core2/Basetype2.cc @@ -5963,7 +5963,7 @@ 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 + if (p_td.json->as_map) { const UNIVERSAL_CHARSTRING* key_ustr = dynamic_cast< const UNIVERSAL_CHARSTRING*>(get_at(0)); if (NULL == key_ustr) { @@ -6016,8 +6016,9 @@ int Record_Type::JSON_encode_negtest(const Erroneous_descriptor_t* p_err_descr, } boolean as_value = NULL != p_td.json && p_td.json->as_value; + boolean as_map = NULL != p_td.json && p_td.json->as_map; - int enc_len = as_value ? 0 : p_tok.put_next_token(JSON_TOKEN_OBJECT_START, NULL); + int enc_len = (as_value || as_map) ? 0 : p_tok.put_next_token(JSON_TOKEN_OBJECT_START, NULL); int values_idx = 0; int edescr_idx = 0; @@ -6031,7 +6032,7 @@ int Record_Type::JSON_encode_negtest(const Erroneous_descriptor_t* p_err_descr, const Erroneous_values_t* err_vals = p_err_descr->next_field_err_values(i, values_idx); const Erroneous_descriptor_t* emb_descr = p_err_descr->next_field_emb_descr(i, edescr_idx); - if (!as_value && NULL != err_vals && NULL != err_vals->before) { + if (!as_value && !as_map && NULL != err_vals && NULL != err_vals->before) { if (NULL == err_vals->before->errval) { TTCN_error("internal error: erroneous before value missing"); } @@ -6057,27 +6058,56 @@ int Record_Type::JSON_encode_negtest(const Erroneous_descriptor_t* p_err_descr, if (NULL == err_vals->value->type_descr) { TTCN_error("internal error: erroneous before typedescriptor missing"); } - // only replace the field's value, keep the field name - if (!as_value) { - enc_len += p_tok.put_next_token(JSON_TOKEN_NAME, field_name); + if (as_map && 0 == i) { + const UNIVERSAL_CHARSTRING* key_ustr = dynamic_cast< + const UNIVERSAL_CHARSTRING*>(err_vals->value->errval); + if (NULL == key_ustr) { + TTCN_EncDec_ErrorContext::error(TTCN_EncDec::ET_UNBOUND, + "Erroneous value for the first field of the 'as map' element type " + "is not a universal charstring"); + } + TTCN_Buffer key_buf; + key_ustr->encode_utf8(key_buf); + CHARSTRING key_str; + key_buf.get_string(key_str); + enc_len += p_tok.put_next_token(JSON_TOKEN_NAME, (const char*) key_str); + } + else { + // only replace the field's value, keep the field name + if (!as_value && !as_map) { + enc_len += p_tok.put_next_token(JSON_TOKEN_NAME, field_name); + } + enc_len += err_vals->value->errval->JSON_encode(*(err_vals->value->type_descr), p_tok); } - enc_len += err_vals->value->errval->JSON_encode(*(err_vals->value->type_descr), p_tok); } } } else { boolean metainfo_unbound = NULL != fld_descr(i)->json && fld_descr(i)->json->metainfo_unbound; if ((NULL != fld_descr(i)->json && fld_descr(i)->json->omit_as_null) || get_at(i)->is_present() || metainfo_unbound || as_value) { - if (!as_value) { + if (!as_value && !as_map) { enc_len += p_tok.put_next_token(JSON_TOKEN_NAME, field_name); } - if (!as_value && metainfo_unbound && !get_at(i)->is_bound()) { + if (!as_value && !as_map && metainfo_unbound && !get_at(i)->is_bound()) { enc_len += p_tok.put_next_token(JSON_TOKEN_LITERAL_NULL); char* metainfo_str = mprintf("metainfo %s", field_name); enc_len += p_tok.put_next_token(JSON_TOKEN_NAME, metainfo_str); Free(metainfo_str); enc_len += p_tok.put_next_token(JSON_TOKEN_STRING, "\"unbound\""); } + else if (as_map && 0 == i) { + 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); + enc_len += p_tok.put_next_token(JSON_TOKEN_NAME, (const char*) key_str); + } else if (NULL != emb_descr) { enc_len += get_at(i)->JSON_encode_negtest(emb_descr, *fld_descr(i), p_tok); } else { @@ -6086,7 +6116,7 @@ int Record_Type::JSON_encode_negtest(const Erroneous_descriptor_t* p_err_descr, } } - if (!as_value && NULL != err_vals && NULL != err_vals->after) { + if (!as_value && !as_map && NULL != err_vals && NULL != err_vals->after) { if (NULL == err_vals->after->errval) { TTCN_error("internal error: erroneous after value missing"); } @@ -6107,7 +6137,7 @@ int Record_Type::JSON_encode_negtest(const Erroneous_descriptor_t* p_err_descr, } } - if (!as_value) { + if (!as_value && !as_map) { enc_len += p_tok.put_next_token(JSON_TOKEN_OBJECT_END, NULL); } return enc_len; diff --git a/regression_test/json/AttributeTestcases.ttcn b/regression_test/json/AttributeTestcases.ttcn index 1485c0d6d44a786e6915ae878cbc79d11b302172..90ef9a4e01548c19573a3d8e2cfaaa8978ec6ffd 100644 --- a/regression_test/json/AttributeTestcases.ttcn +++ b/regression_test/json/AttributeTestcases.ttcn @@ -675,9 +675,10 @@ 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}"); +// Testing the attribute 'as map' +testcase tc_attribute_as_map() runs on MTC { + var Map x := { { "one", 1 }, { "two", 2 }, { "three", 3 }, { "zero", omit } }; + var octetstring enc_exp := char2oct("{\"one\":1,\"two\":2,\"three\":3,\"zero\":null}"); var octetstring enc := f_enc_map(x); if (enc != enc_exp) { setverdict(fail, "Encoding failed. Expected: ", enc_exp, ", got: ", enc); @@ -689,6 +690,47 @@ testcase tc_attribute_map() runs on MTC { setverdict(pass); } +// Testing the attribute 'as map' in a union with the attribute 'as value' +testcase tc_attribute_as_map_in_union() runs on MTC { + var JsonValue x := { + JsonObject := { + { + key := "num", + val := { JsonInteger := 2 } + }, + { + key := "str", + val := { JsonString := "abc" } + } + } + }; + var octetstring enc_exp := char2oct("{\"num\":2,\"str\":\"abc\"}"); + var octetstring enc := f_enc_json_val(x); + if (enc != enc_exp) { + setverdict(fail, "Encoding failed. Expected: ", enc_exp, ", got: ", enc); + } + var JsonValue dec := f_dec_json_val(enc_exp); + if (dec != x) { + setverdict(fail, "Decoding failed. Expected: ", x, ", got: ", dec); + } + setverdict(pass); +} + +// Testing the attribute 'text ... as ...' +testcase tc_attribute_text_as() runs on MTC { + var EnumNumberList x := { One, Two, Three }; + var octetstring enc_exp := char2oct("[\"1\",\"2\",\"3\"]"); + var octetstring enc := f_enc_enum_list(x); + if (enc != enc_exp) { + setverdict(fail, "Encoding failed. Expected: ", enc_exp, ", got: ", enc); + } + var EnumNumberList dec := f_dec_enum_list(enc_exp); + if (dec != x) { + setverdict(fail, "Decoding failed. Expected: ", x, ", got: ", dec); + } + setverdict(pass); +} + control { execute(tc_NoAttributeOnUpperLevel()) @@ -718,6 +760,8 @@ control { execute(tc_attribute_chosen()); execute(tc_attribute_chosen_negtest()); execute(tc_attribute_chosen_default()); - execute(tc_attribute_map()); + execute(tc_attribute_as_map()); + execute(tc_attribute_as_map_in_union()); + execute(tc_attribute_text_as()); } } diff --git a/regression_test/json/Functions.ttcn b/regression_test/json/Functions.ttcn index d877ecd96f3ec2fecab74bbc6f36c7a60299bf1a..003b7b51fd67a47c14e421bf2d2943805780e69d 100644 --- a/regression_test/json/Functions.ttcn +++ b/regression_test/json/Functions.ttcn @@ -206,6 +206,12 @@ external function f_enc_multi_list(in MultiLevelList x) return octetstring external function f_enc_map(in Map x) return octetstring with { extension "prototype(convert) encode(JSON)" } +external function f_enc_json_val(in JsonValue x) return octetstring + with { extension "prototype(convert) encode(JSON)" } + +external function f_enc_enum_list(in EnumNumberList 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)" } @@ -411,6 +417,12 @@ external function f_dec_multi_list(in octetstring x) return MultiLevelList external function f_dec_map(in octetstring x) return Map with { extension "prototype(convert) decode(JSON)" } +external function f_dec_json_val(in octetstring x) return JsonValue + with { extension "prototype(convert) decode(JSON)" } + +external function f_dec_enum_list(in octetstring x) return EnumNumberList + 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 15d59c6b4e7ed9acb9cf0f58590f93423eb11676..2bc86553879a93b594ffad85de242194b1dc3902 100644 --- a/regression_test/json/Types.ttcn +++ b/regression_test/json/Types.ttcn @@ -364,12 +364,37 @@ with { type record MapItem { universal charstring key, - integer value_ + integer value_ optional } type set of MapItem Map with { variant "as map" } +type union JsonValue { + integer JsonInteger, + float JsonNumber, + boolean JsonBool, + universal charstring JsonString, + record of JsonValue JsonArray, + set of record { + universal charstring key, + JsonValue val + } JsonObject +} +with { + variant "JSON: as value" + variant (JsonObject) "as map"; +} + +type enumerated EnumNumber { One, Two, Three } +with { + variant "text 'One' as '1'"; + variant "text 'Two' as '2'"; + variant "text 'Three' as '3'"; +} + +type record of EnumNumber EnumNumberList; + } with { encode "JSON"; extension "anytype integer, charstring, R, RoI, Thing"; diff --git a/usrguide/referenceguide/4-ttcn3_language_extensions.adoc b/usrguide/referenceguide/4-ttcn3_language_extensions.adoc index 1e9471551149639cbb2dac6728199eb1cc1a1332..8e7ca504b3f0a558d9a6cda0482cd4ddabf414ad 100644 --- a/usrguide/referenceguide/4-ttcn3_language_extensions.adoc +++ b/usrguide/referenceguide/4-ttcn3_language_extensions.adoc @@ -5678,6 +5678,56 @@ type union Choices { ---- +*As map* + +Attribute syntax: as map + +Applicable to (TTCN-3): Record of/set of with a record/set element type, that has 2 fields, the first of which is a non-optional universal charstring + +Description: If set, the mentioned structure is encoded as a JSON object containing key-value pairs. The universal charstrings in the element records/sets are the keys, and the second field in each record/set contains the value. This allows the creation of heterogenous objects in the JSON document (i.e. JSON objects with any combination of field names and values). + +Affects both encoding and decoding. + +Example: +[source] +---- +type record MapItem { + universal charstring key, + integer value_ optional +} + +type set of MapItem Map +with { variant "as map" } + +const Map c_map := { { "one", 1 }, { "two", 2 }, { "three", 3 }, { "zero", omit } }; +// is encoded into: { "one" : 1, "two" : 2, "three" : 3, "zero" : null } +---- + +*Text ... as ...* + +Attribute syntax: text '<enum text>' as '<new text>' + +Applicable to (TTCN-3): Enumerated types + +Description: This attribute can be used to change the encoding of certain enumerated values. Each attribute changes the encoding of one enumerated option. + +Affects both encoding and decoding. + +Example: +[source] +---- +type enumerated EnumNumber { One, Two, Three } +with { + variant "text 'One' as '1'"; + variant "text 'Two' as '2'"; + variant "text 'Three' as '3'"; +} +type record of EnumNumber EnumNumberList; + +const EnumNumberList c_numbers := { One, Two, Three }; +// is encoded into: [ "1", "2", "3" ] +---- + [[external-functions-0]] === External functions diff --git a/usrguide/referenceguide/ReferenceGuide.pdf b/usrguide/referenceguide/ReferenceGuide.pdf index 878057efa8e9847394d6955d2a7f4c5de9b7a349..0047a4001916ae626a011d5791b3b8724d336c98 100644 Binary files a/usrguide/referenceguide/ReferenceGuide.pdf and b/usrguide/referenceguide/ReferenceGuide.pdf differ