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