2

How can i parse and show error if given JSON contains duplicate keys. JSON.parse just ignores it & pick last key value.Also let me know if any sort of npm lib available for the same.

{ "name":"mohit", "name":"verma" } 
7
  • thats fine, but what if i get json with duplicate keys. Even JSON.parse does not throw error. Commented Dec 4, 2016 at 6:51
  • stackoverflow.com/questions/21832701/… Commented Dec 4, 2016 at 6:52
  • New duplicate keys will just overwrite the existing ones Commented Dec 4, 2016 at 6:53
  • 1
    Fix the "system" creates such an abomination - that would be the easiest "fix" Commented Dec 4, 2016 at 7:10
  • 1
    Sadly, the reviver parameter to JSON.parse does not seem to be called multiple times if there are duplicate keys. Commented Dec 4, 2016 at 9:22

2 Answers 2

4

If you can predict how the JSON will be formatted*, you can compare to the text to the result of parsing and re-stringifying the object:

  • See comments below for a description of cases where this would fail

const hasDuplicateKey = (j) => { let k = JSON.stringify(JSON.parse(j)); let h = JSON.stringify(JSON.parse(j),null," "); return !(j === h || j === k); }; let json1 = `{"name":"bob","name":"alice","age":7}`; let json2 = `{"name":"bob","age":7}`; let json3 = `{ "name": "mohit", "name": "verma" }`; let json4 = `{ "name": "mohit", "age": 107 }`; console.log( hasDuplicateKey(json1) ); console.log( hasDuplicateKey(json2) ); console.log( hasDuplicateKey(json3) ); console.log( hasDuplicateKey(json4) );

Sign up to request clarification or add additional context in comments.

7 Comments

That doesn't work. Passing JSON through parse and then stringify will normalise things like white space. You've got k and h to handle two different sets of normalisation, but that only works if the initial data meets those normalisation patterns. Change one bit of it to let json2 = {"name": "bob","age":7}; (with extra spaces) and you get false positives.
that is why i qualified with "if you can predict how the JSON will be formatted"
@code_monk: If if one can "predict" how the JSON is formatted, it still would have to follow the same formatting rules as JSON.stringify. So why not just say that? "If the JSON is formatted the same way as the output produced by JSON.stringify, ....".
This will fail on an input of '{"a":1,"1":22}' (in at least some native JSON.parse/JSON.stringify implementations, due to special rules for ordering numeric keys). Your condition of "works with" is too weak--it needs to say "if it is guaranteed that JSON.stringify on the result of JSON.parse is byte-for-byte identical with the original input to JSON.parse". However, this condition never holds for any known JSON libraries. Hence, this solution at best works only in limited situations.
can we remove whitespace from json string, then parse and stringify it & match with string ( in which white space was removed )
|
1

The JSON format does not forbid duplicate keys, but if you want to reject such, then you could write your own parser that includes such a requirement.

That custom JSON parser could first call the native JSON.parse to verify that the JSON is valid, so the rest of your code does not have to test for invalid input.

One idea is that such a parser would collect key/value pairs first in an array of pairs, and would then pass that array to a user-provided callback that by default creates a plain object from it. This will allow you to raise an error if that array of entries has duplicate keys (for instance).

Here is an implementation where you can enter your JSON and which displays the result: "No duplicate keys found", or "Duplicate key found at "

function parseJson(json, entriesMapper=(_,arr) => Object.fromEntries(arr)) { // Ensure input is valid JSON const result = JSON.parse(json); // This regex tokenizes a JSON that is assumed(!) valid const regex = /"(?:\\?.)*?"|[\d.eE+-]+|\w+|[^:\s]/g; const tokens = json.matchAll(regex).map(([x]) => x); // Functions to look ahead on above tokens iterator let current = tokens.next(); const peek = () => current.done ? "" : current.value; const next = () => [peek(), current = tokens.next()][0]; const is = (what) => !!(what === peek() && next()); // Function to parse a collection as entries and allow a callback // to do some post processing before creating the final object function parseCollection(path, obj, getKey, entriesMapper) { let entries = []; let key; const [, end] = JSON.stringify(obj); // Get delimiter next(); // skip start symbol if (!is(end)) { do { entries.push([key = getKey(), parseValue(`${path}.${key}`)]); } while (is(",")); next(); // skip end symbol } return entriesMapper(path, entries); } const indexer = () => (index=0) => index++; const parsePrimitive = () => JSON.parse(next()); const parseObject = (path) => parseCollection(path, {}, parsePrimitive, entriesMapper); const parseArray = (path) => parseCollection(path, [], indexer()); const parseValue = (path, value=peek()) => value === "{" ? parseObject(path) : value === "[" ? parseArray(path) : parsePrimitive(); parseValue("root"); // Custom parse... return result; } function requireUniqueKeys(path, entries) { let keySet = new Set; for (const [key] of entries) { if (keySet.has(key)) throw Error(`Duplicate key found at '${path}.${key}'`); keySet.add(key); } return Object.fromEntries(entries); // All good: create the object } // I/O handling const [input, output] = document.querySelectorAll("textarea, div"); input.addEventListener("input", refresh); function refresh() { const json = input.value; try { const parsed = parseJson(json, requireUniqueKeys); output.textContent = parsed === undefined ? "Not valid JSON" : "No duplicate keys found"; } catch (e) { output.textContent = e.message; } } refresh();
textarea { width: 100%; height: 50vh; box-sizing: border-box; }
<b>Input JSON:</b><br> <textarea> { "x": [{ "a": 1, "b": 3, "a": 2 }], "w": 123, "e": 1 } </textarea><br> <b>Validation:</b> <div></div>

This callback mechanism allows you to deal with duplicate keys in different ways. Instead of throwing an error you could also decide to modify the key/value pairs to somehow resolve that duplication. For instance, by combining the values that are associated with the same key into a singly key/value pair, and then create the object from that (using Object.fromEntries).

1 Comment

Custom parsing is the only valid answer here. If not done by hand, then by some package that will still have to do the parsing for you. So, this answer is about the best you can have.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.