I've done something similar in a SP list form. I created a custom front-end for the form to allow the user to create "lines" for more key/value information (in my case it was "relevant procedures" which included a Title and a URL) which I then stored in a multi-line text field as HTML. Later when displaying the data on the edit form, I grabbed the value of the multi-line text field and parsed it back into the desired format. For the display, I was pulling back the data via REST, so I could simply plop the HTML on the page for a nice list display of relevant procedure links. I imagine something like this would work for your scenario as well.
Here's some sampling of my solution (attribute names have been changed to protect the innocent):
//HTML on custom SP form
<div class='column-row'>
<div class='column full'>
<label class="relevant-procedures-label">
Related Topics
<div class="column-row relevant-procedures-head">
<div class="column three-sevenths relevant-procedures">
<span>Title</span>
</div>
<div class="column three-sevenths relevant-procedures">
<span>URL</span>
</div>
<div class="column one-seventh relevant-procedures">
<span class="icon icon-plus add-relevant-procedure"></span>
</div>
</div>
<div class="column-row relevant-procedures-row" id="relevant-procedures-row-1">
<div class="column three-sevenths relevant-procedures">
<input type="text" class="relevant-procedure-title" />
</div>
<div class="column three-sevenths relevant-procedures">
<input type="text" class="relevant-procedure-url" />
</div>
<div class="column one-seventh relevant-procedures">
<span class="icon icon-cross remove-relevant-procedure"></span>
</div>
</div>
<span class="tsp-form" data-fin="RelevantProcedures" style="display: none;"></span>
</label>
</div>
</div>
// relevant procedures click handler (add row)
jQuery(".page-content").on("click", ".add-relevant-procedure", function () {
var rowCount = jQuery(".relevant-procedures-row").length;
var nextRowId = "relevant-procedures-row-" + (rowCount + 1);
var newRow = jQuery("#relevant-procedures-row-1").clone().attr("id", nextRowId);
newRow.find("input").val("");
newRow.appendTo(".relevant-procedures-label");
});
// relevant procedures click handler (remove row)
jQuery(".page-content").on ("click", ".remove-relevant-procedure", function () {
var thisRow = jQuery(this).closest(".relevant-procedures-row");
if (thisRow.attr("id").slice(-1) == 1) {
thisRow.find("input").val("");
}
else {
thisRow.remove();
}
});
// check/set relevant procedures on save (check if values and save to multi-line field as unordered list)
if (jQuery(".relevant-procedure-title").first().val()) {
var relevantProcedures = '<ul>';
jQuery(".relevant-procedures-row").each(function () {
var title = jQuery(this).find(".relevant-procedure-title").val();
var url = jQuery(this).find(".relevant-procedure-url").val();
relevantProcedures += '<li><a href="' + url + '">' + title + '</a></li>';
});
relevantProcedures += '</ul>';
setVal("RelevantProcedures", relevantProcedures);
}
// create relevant procedures table (put stored HTML back in inputs for editform)
if (getVal("RelevantProcedures").indexOf('<li>') > -1) {
getField("RelevantProcedures").find("li").each(function () {
var title = jQuery(this).text();
var url = jQuery(this).attr("href");
var rowCount = jQuery(".relevant-procedures-row").length;
var nextRowId = "relevant-procedures-row-" + (rowCount + 1);
var newRow = jQuery("#relevant-procedures-row-1").clone().attr("id", nextRowId);
newRow.find(".relevant-procedure-title").val(title);
newRow.find(".relevant-procedure-url").val(url);
newRow.appendTo(".relevant-procedures-label");
});
}
There are some helper functions I've made which are used in the code sample (getVal, getField, etc.), so you will have to replicate the functionality manually or create your own helpers.
Here's how it looks on the form:
Here's how the display looks:
I hope this helps to give you an idea of how you can meet your requirements. Please let me know if you have any questions.