json parser FEATURE support attributes
diff --git a/src/parser_json.c b/src/parser_json.c
index da15292..21be836 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -244,7 +244,7 @@
 
         switch(data[len]) {
         case '0':
-            if (!i && data[len + 1] != ';' && !lyjson_isspace(data[len + 1])) {
+            if (!i && isdigit(data[len + 1])) {
                 /* leading 0 is not allowed */
                 LOGVAL(LYE_XML_INVAL, lineno, "JSON number (leading zero)");
                 return 0;
@@ -489,16 +489,132 @@
 }
 
 static unsigned int
+json_parse_attr(struct lys_module *parent_module, struct lyd_attr **attr, const char *data)
+{
+    unsigned int len = 0, r;
+    char *str = NULL, *name, *prefix, *value;
+    struct lys_module *module = parent_module;
+    struct lyd_attr *attr_new, *attr_last = NULL;
+
+    if (data[len] != '{') {
+        if (!strncmp(&data[len], "null", 4)) {
+            *attr = NULL;
+            len += 4;
+            len += skip_ws(&data[len]);
+            return len;
+        }
+        LOGVAL(LYE_XML_INVAL, lineno, "JSON data (missing begin-object)");
+        goto error;
+    }
+
+repeat:
+    len++;
+    len += skip_ws(&data[len]);
+
+    if (data[len] != '"') {
+        LOGVAL(LYE_XML_INVAL, lineno, "JSON data (missing quotation-mark at the begining of string)");
+        return 0;
+    }
+    len++;
+    str = lyjson_parse_text(&data[len], &r);
+    if (!r) {
+        goto error;
+    } else if (data[len + r] != '"') {
+        LOGVAL(LYE_XML_INVAL, lineno, "JSON data (missing quotation-mark at the end of string)");
+        goto error;
+    }
+    if ((name = strchr(str, ':'))) {
+        *name = '\0';
+        name++;
+        prefix = str;
+        module = ly_ctx_get_module(parent_module->ctx, prefix, NULL);
+        if (!module) {
+            LOGVAL(LYE_INELEM, lineno, name);
+            goto error;
+        }
+    } else {
+        name = str;
+    }
+
+    /* prepare data for parsing node content */
+    len += r + 1;
+    len += skip_ws(&data[len]);
+    if (data[len] != ':') {
+        LOGVAL(LYE_XML_INVAL, lineno, "JSON data (missing name-separator)");
+        goto error;
+    }
+    len++;
+    len += skip_ws(&data[len]);
+
+    if (data[len] != '"') {
+        LOGVAL(LYE_XML_INVAL, lineno, "JSON data (missing quotation-mark at the beginning of string)");
+        return 0;
+    }
+    len++;
+    value = lyjson_parse_text(&data[len], &r);
+    if (!r) {
+        goto error;
+    } else if (data[len + r] != '"') {
+        LOGVAL(LYE_XML_INVAL, lineno, "JSON data (missing quotation-mark at the end of string)");
+        goto error;
+    }
+    len += r + 1;
+    len += skip_ws(&data[len]);
+
+    attr_new = malloc(sizeof **attr);
+    attr_new->module = module;
+    attr_new->next = NULL;
+    attr_new->name = lydict_insert(module->ctx, name, 0);
+    attr_new->value = lydict_insert_zc(module->ctx, value);
+    if (!attr_last) {
+        *attr = attr_last = attr_new;
+    } else {
+        attr_last->next = attr_new;
+        attr_last = attr_new;
+    }
+
+    free(str);
+
+    if (data[len] == ',') {
+        goto repeat;
+    } else if (data[len] != '}') {
+        LOGVAL(LYE_XML_INVAL, lineno, "JSON data (missing end-object)");
+        return 0;
+    }
+    len++;
+    len += skip_ws(&data[len]);
+
+    return len;
+
+error:
+    free(str);
+    return 0;
+}
+
+struct attr_cont {
+    struct attr_cont *next;
+    struct lyd_attr *attr;
+    struct lys_node *schema;
+    unsigned int index;    /** non-zero only in case of leaf-list */
+};
+
+static unsigned int
 json_parse_data(struct ly_ctx *ctx, const char *data, struct lyd_node **parent, struct lyd_node *prev,
-                int options, struct unres_data *unres)
+                struct attr_cont **attrs, int options, struct unres_data *unres)
 {
     unsigned int len = 0;
     unsigned int r;
-    int flag_object = 0;
-    char *name, *prefix = NULL, *str;
+    unsigned int flag_object = 0, flag_leaflist = 0;
+    char *name, *prefix = NULL, *str = NULL;
     struct lys_module *module = NULL;
     struct lys_node *schema = NULL;
     struct lyd_node *result = NULL, *new, *list, *diter = NULL;
+    struct lyd_attr *attr;
+    struct attr_cont *attrs_new, *attrs_start = NULL;
+
+    if (!attrs) {
+        attrs = &attrs_start;
+    }
 
     /* skip leading whitespaces */
     len += skip_ws(&data[len]);
@@ -512,7 +628,8 @@
 
     /* each YANG data node representation starts with string (node identifier) */
     if (data[len] != '"') {
-        goto error;
+        LOGVAL(LYE_XML_INVAL, lineno, "JSON data (missing quotation-mark at the beginning of string)");
+        return 0;
     }
     len++;
 
@@ -527,12 +644,16 @@
         *name = '\0';
         name++;
         prefix = str;
+        if (prefix[0] == '@') {
+            prefix++;
+        }
     } else {
         name = str;
+        if (name[0] == '@') {
+            name++;
+        }
     }
 
-    /* TODO - process attributes (@) */
-
     /* prepare data for parsing node content */
     len += r + 1;
     len += skip_ws(&data[len]);
@@ -543,6 +664,28 @@
     len++;
     len += skip_ws(&data[len]);
 
+    if (str[0] == '@' && !str[1]) {
+        /* process attribute of the parent object (container or list) */
+        if (!*parent) {
+            LOGVAL(LYE_XML_INVAL, lineno, "attribute with no corresponding element to belongs to");
+            goto error;
+        }
+
+        r = json_parse_attr((*parent)->schema->module, &attr, &data[len]);
+        if (!r) {
+            goto error;
+        }
+        len += r;
+
+        if ((*parent)->attr) {
+            lyd_free_attr(ctx, NULL, attr, 1);
+        } else {
+            (*parent)->attr = attr;
+        }
+        result = prev;
+        goto siblings;
+    }
+
     /* find schema node */
     if (!(*parent)) {
         /* starting in root */
@@ -555,6 +698,9 @@
                     break;
                 }
             }
+        } else {
+            LOGVAL(LYE_INELEM, lineno, name);
+            goto error;
         }
     } else {
         /* parsing some internal node, we start with parent's schema pointer */
@@ -563,8 +709,7 @@
             module = ly_ctx_get_module(ctx, prefix, NULL);
             if (!module) {
                 LOGVAL(LYE_INELEM, lineno, name);
-                free(prefix);
-                return 0;
+                goto error;
             }
         }
         while ((schema = lys_getnext(schema, (*parent)->schema, module, 0))) {
@@ -575,10 +720,60 @@
     }
     if (!schema) {
         LOGVAL(LYE_INELEM, lineno, name);
-        free(str);
-        return 0;
+        goto error;
     }
+
+    if (str[0] == '@') {
+        free(str);
+        str = NULL;
+
+        /* attribute for some sibling node */
+        if (data[len] == '[') {
+            flag_leaflist = 1;
+            len++;
+            len += skip_ws(&data[len]);
+        }
+
+attr_repeat:
+        r = json_parse_attr(schema->module, &attr, &data[len]);
+        if (!r) {
+            goto error;
+        }
+        len += r;
+
+        if (attr) {
+            attrs_new = malloc(sizeof *attrs_new);
+            attrs_new->attr = attr;
+            attrs_new->index = flag_leaflist;
+            attrs_new->schema = schema;
+            attrs_new->next = *attrs;
+            *attrs = attrs_new;
+        } else if (!flag_leaflist) {
+            /* error */
+            LOGVAL(LYE_XML_INVAL, lineno, "attribute data");
+            goto error;
+        }
+
+        if (flag_leaflist) {
+            if (data[len] == ',') {
+                len++;
+                len += skip_ws(&data[len]);
+                flag_leaflist++;
+                goto attr_repeat;
+            } else if (data[len] != ']') {
+                LOGVAL(LYE_XML_INVAL, lineno, "JSON data (missing end-array)");
+                goto error;
+            }
+            len++;
+            len += skip_ws(&data[len]);
+        }
+
+        result = prev;
+        goto siblings;
+    }
+
     free(str);
+    str = NULL;
 
     switch (schema->nodetype) {
     case LYS_CONTAINER:
@@ -625,6 +820,10 @@
         if (!r) {
             goto error;
         }
+        while(result->next) {
+            result = result->next;
+        }
+
         len += r;
         len += skip_ws(&data[len]);
     } else if (schema->nodetype == LYS_ANYXML) {
@@ -644,7 +843,7 @@
 
         if (data[len] != '}') {
             /* non-empty container */
-            r = json_parse_data(ctx, &data[len], &result, NULL, options, unres);
+            r = json_parse_data(ctx, &data[len], &result, NULL, NULL, options, unres);
             if (!r) {
                 goto error;
             }
@@ -680,7 +879,7 @@
                 }
             }
 
-            r = json_parse_data(ctx, &data[len], &list, NULL, options, unres);
+            r = json_parse_data(ctx, &data[len], &list, NULL, NULL, options, unres);
             if (!r) {
                 goto error;
             }
@@ -728,13 +927,18 @@
         }
     }
 
+    if (!(*parent)) {
+        *parent = result;
+    }
+
+siblings:
     /* process siblings */
     if (data[len] && data[len] == ',') {
         /* have siblings */
         len++;
         len += skip_ws(&data[len]);
 
-        r = json_parse_data(ctx, &data[len], parent, result, options, unres);
+        r = json_parse_data(ctx, &data[len], parent, result, attrs, options, unres);
         if (!r) {
             goto error;
         }
@@ -744,6 +948,47 @@
         len += skip_ws(&data[len]);
     }
 
+    if (!prev) {
+        /* first sibling - now process the sibling attributes since there must be a node for them */
+        while (*attrs) {
+            attrs_new = *attrs;
+            *attrs = (*attrs)->next;
+
+            if (attrs_new->index) {
+                flag_leaflist = 1;
+            }
+
+            LY_TREE_FOR(result, diter) {
+                if (attrs_new->schema != diter->schema) {
+                    continue;
+                }
+
+                if (flag_leaflist && flag_leaflist != attrs_new->index) {
+                    flag_leaflist++;
+                    continue;
+                }
+
+                /* we have match */
+                if (diter->attr) {
+                    LOGVAL(LYE_XML_INVAL, lineno, "attribute (multiple attribute definitions belong to a single element)");
+                    free(attrs_new);
+                    goto error;
+                }
+
+                diter->attr = attrs_new->attr;
+                break;
+            }
+
+            if (!diter) {
+                LOGVAL(LYE_XML_INVAL, lineno, "attribute with no corresponding element to belongs to");
+                lyd_free_attr(attrs_new->schema->module->ctx, NULL, attrs_new->attr, 1);
+                free(attrs_new);
+                goto error;
+            }
+            free(attrs_new);
+        }
+    }
+
     if (flag_object) {
         if (data[len] != '}') {
             /* expecting end-object */
@@ -754,14 +999,23 @@
         len += skip_ws(&data[len]);
     }
 
-
-    if (!(*parent)) {
-        *parent = result;
-    }
     return len;
 
 error:
+    if ((*parent) == result) {
+        (*parent) = NULL;
+    }
+    while (*attrs) {
+        attrs_new = *attrs;
+        *attrs = (*attrs)->next;
+
+        lyd_free_attr(ctx, NULL, attrs_new->attr, 1);
+        free(attrs_new);
+    }
+
     lyd_free(result);
+    free(str);
+
     return 0;
 }
 
@@ -783,7 +1037,7 @@
 #endif
     ly_errno = 0;
     result = NULL;
-    json_parse_data(ctx, data, &result, NULL, options, unres);
+    json_parse_data(ctx, data, &result, NULL, NULL, options, unres);
 
     /* check leafrefs and/or instids if any */
     if (result && resolve_unres_data(unres)) {
diff --git a/src/printer_json.c b/src/printer_json.c
index ab1cb53..8d7ea4a 100644
--- a/src/printer_json.c
+++ b/src/printer_json.c
@@ -110,11 +110,10 @@
 
     /* print attributes as sibling leaf */
     if (!onlyvalue && node->attr) {
-        ly_print(out, "\n");
         if (schema) {
-            ly_print(out, "%*s\"@%s:%s\": {\n", LEVEL, INDENT, schema, node->schema->name);
+            ly_print(out, ",\n%*s\"@%s:%s\": {\n", LEVEL, INDENT, schema, node->schema->name);
         } else {
-            ly_print(out, "%*s\"@%s\": {\n", LEVEL, INDENT, node->schema->name);
+            ly_print(out, ",\n%*s\"@%s\": {\n", LEVEL, INDENT, node->schema->name);
         }
         json_print_attrs(out, level + 1, node);
         ly_print(out, "%*s}", LEVEL, INDENT);
@@ -224,9 +223,9 @@
     /* attributes */
     if (!is_list && flag_attrs) {
         if (schema) {
-            ly_print(out, "\n%*s\"@%s:%s\": [\n", LEVEL, INDENT, schema, node->schema->name);
+            ly_print(out, ",\n%*s\"@%s:%s\": [\n", LEVEL, INDENT, schema, node->schema->name);
         } else {
-            ly_print(out, "\n%*s\"@%s\": [\n", LEVEL, INDENT, node->schema->name);
+            ly_print(out, ",\n%*s\"@%s\": [\n", LEVEL, INDENT, node->schema->name);
         }
         level++;
         for (list = node; list; ) {
@@ -269,11 +268,10 @@
 
     /* print attributes as sibling leaf */
     if (node->attr) {
-        ly_print(out, "\n");
         if (schema) {
-            ly_print(out, "%*s\"@%s:%s\": {\n", LEVEL, INDENT, schema, node->schema->name);
+            ly_print(out, ",\n%*s\"@%s:%s\": {\n", LEVEL, INDENT, schema, node->schema->name);
         } else {
-            ly_print(out, "%*s\"@%s\": {\n", LEVEL, INDENT, node->schema->name);
+            ly_print(out, ",\n%*s\"@%s\": {\n", LEVEL, INDENT, node->schema->name);
         }
         json_print_attrs(out, level + 1, node);
         ly_print(out, "%*s}", LEVEL, INDENT);
diff --git a/src/tree_data.c b/src/tree_data.c
index aca7787..9a767a2 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -602,19 +602,46 @@
     return ret;
 }
 
-static void
-lyd_attr_free(struct ly_ctx *ctx, struct lyd_attr *attr)
+API void
+lyd_free_attr(struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_attr *attr, int recursive)
 {
-    if (!attr) {
+    struct lyd_attr *iter;
+
+    if (!ctx || !attr) {
         return;
     }
 
-    if (attr->next) {
-        lyd_attr_free(ctx, attr->next);
+    if (parent) {
+        if (parent->attr == attr) {
+            if (recursive) {
+                parent->attr = NULL;
+            } else {
+                parent->attr = attr->next;
+            }
+        } else {
+            for (iter = parent->attr; iter->next != attr; iter = iter->next);
+            if (iter->next) {
+                if (recursive) {
+                    iter->next = NULL;
+                } else {
+                    iter->next = attr->next;
+                }
+            }
+        }
     }
-    lydict_remove(ctx, attr->name);
-    lydict_remove(ctx, attr->value);
-    free(attr);
+
+    if (!recursive) {
+        attr->next = NULL;
+    }
+
+    for(iter = attr; iter; ) {
+        attr = iter;
+        iter = iter->next;
+
+        lydict_remove(ctx, attr->name);
+        lydict_remove(ctx, attr->value);
+        free(attr);
+    }
 }
 
 struct lyd_node *
@@ -717,7 +744,7 @@
     }
 
     lyd_unlink(node);
-    lyd_attr_free(node->schema->module->ctx, node->attr);
+    lyd_free_attr(node->schema->module->ctx, node, node->attr, 1);
     free(node);
 }
 
diff --git a/src/tree_data.h b/src/tree_data.h
index 74d42b8..150f9e8 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -299,6 +299,20 @@
 struct lyd_attr *lyd_insert_attr(struct lyd_node *parent, const char *name, const char *value);
 
 /**
+ * @brief Destroy data attribute
+ *
+ * If the attribute to destroy is a member of a node attribute list, it is necessary to
+ * provide the node itself as \p parent to keep the list consistent.
+ *
+ * @param[in] ctx Context where the attribute was created (usually it is the context of the \p parent)
+ * @param[in] parent Parent node where the attribute is placed
+ * @param[in] attr Attribute to destroy
+ * @param[in] recursive Zero to destroy only the attribute, non-zero to destroy also all the subsequent attributes
+ *            in the list.
+ */
+void lyd_free_attr(struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_attr *attr, int recursive);
+
+/**
  * @brief Opaque internal structure, do not access it from outside.
  */
 struct lyxml_elem;