How can you parse NON wellformed JSON (maybe using jQuery) - javascript

Is there a way to parse NON wellformed JSON, other than using eval?
The background is, that I'm using data tag values to draw a graph like this:
<div id="data" data-idata="[1,2,3]" data-sdata="['foo','bar','baz']"></div>
This works flawlessly with numeric values, these values are delivered as an array directly in jQuery data, no need to parse JSON here.
However, for the labels a string array is to be passed. eval can parse the string in sdata just fine, but JSON.parse and jQuery.parseJSON fail, because it's not wellformed JSON.
var $data = $("#data").data(),
values;
// use eval
out("#out", eval($data.sdata)); // works...
// use JSON.parse
try
{
values = JSON.parse($data.sdata);
} catch(e) {
// silent catch
}
out("#out1", values); // values is undefined
I put together a JsFiddle here to check the thing.

You get error because ['foo','bar','baz'] contains single-quotation marks. JSON RFC specifies that string should be enclosed in double-quotation marks.
There could be a few work-arounds.
Switch quotation marks in the data- attributes:
<tag data-sdata='["foo","bar","baz"]' />
Or replace in Javascript:
values = JSON.parse($data.sdata.replace("'","\""));

You don't need to parse. Simply use .data(), the function does it for you.
I have changed your HTML, I have swapped quotes in line data-sdata="['foo', 'bar', 'baz']" as JSON should use in double-quotation(") marks.
HTML
<div id="data" data-idata="[1,2,3]" data-sdata='["foo", "bar", "baz"]'></div>
Script
out("#out1", $("#data").data('idata'));
out("#out2", $("#data").data('sdata'));
DEMO

Ofcourse you cannot easy parse corrupted data for 100% this goes for any data-formats and not only JSON.
If the reason the data are malformed are always the same you should fix it with some str_find and replacement functions.
If it is irregular you have no real chance except writing a really intelligenz and big algorithm. The best way here would be to try to extract the real raw data out of the corrupted string and build a real JSON string with valid syntax.

Try
html
<div id="data" data-idata="[1,2,3]" data-sdata='["foo","bar","baz"]'></div>
<div id="out1">out1</div>
<div id="out2">out2</div>
js
$(function () {
var out = function (outputId, elem, dataId) {
return $.each(JSON.parse($(elem).get(0).dataset["" + dataId])
, function (index, value) {
$("<ul>").html("<li>" + index + " -> " + value + "</li>")
.appendTo(outputId);
});
};
out("#out1", "#data", "idata");
out("#out2", "#data", "sdata");
});
jsfiddle http://jsfiddle.net/guest271314/4mJBp/

Related

How to extract with jQuery json from data attribute with &quot inside?

I store JSON in a data attribute, which contains &quot:
<div id="tt" data-json='{"t":"title: "BB""}'></div>
I get the data-json value with jQuery:
aa1 = $('#tt').data('json');
However, I get a string aa1, not an object as I would expect. If I delete &quot from the JSON, then I get an object in aa1.
If I do so:
aa1_str = $('#tt').attr('data-json');
aa1 = JSON.parse(aa1_str);
I am getting an error in JSON.parse:
Uncaught SyntaxError: Unexpected token B in JSON at position 14
$('#tt').attr('data-json') returns an already parsed string, where &quot is replaced with ".
How can I correctly extract data from a JSON string which contains &quot?
The problem is that the " HTML entity gets decoded to a " before jQuery can parse the object. This turns the value in to {"t":"title: "BB""}, which is not a valid JSON string, hence jQuery does not parse it to an object for you and returns the original string.
To correct this you can use other HTML entities to encode the quotes, such as “ and ” or “ and ”, the latter of which I used in the following working example:
let aa1 = $('#tt').data('json');
console.log(aa1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="tt" data-json='{"t":"title: “BB”"}'></div>
That being said, I would suggest a different approach; remove the need for the quotes at all. Just return the BB value in the title property and format the output to include title: in the UI, if necessary. For example:
<div id="tt" data-json='{"title": "BB"}'></div>
Maybe you can use decodeURIComponent
https://www.w3schools.com/jsref/jsref_decodeuricomponent.asp
aa1_str = $('#tt').attr('data-json');
var uri_enc = encodeURIComponent(aa1_str); // before you save the content
console.log(uri_enc);
var uri_dec = decodeURIComponent(aa1_str); // before you display the content
$("#test").text(uri_dec);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="tt" data-json='{"t":"title: "BB""}'></div>
<div id="test"></div>
I believe the problem is with your syntax in the attribute. JavaScript is, to my knowledge, unable to distinguish " form ". Hence when you get the attribute, what is returned is: {"t":"title: "BB""}. This cannot be a valid JSON.
My suggestion is to escape using simple backslash instead of special character escapes. So,
HTML
<div id="tt" data-json='{"t":"title: \"BB\""}'></div>
JQUERY
var aa1 = $('#tt').attr('data-json');
var jsonAa1 = JSON.parse(aa1);
Now jsonAa1 is a valid JSON object that you can use as you please.

Jquery data() returning undefined on CSS selector

I was testing out the data() method of jQuery which seems to be very powerful. This is actually for testing out a fixture I'm going to be making for a unit test (that parses out JSON elements by replacing the single quotes with double quotes).
var test = $('#test');
console.log(test.data('table[data-table-values]'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="test" data-table-values="'{'header': ['value1', 'value2']}'"></table>
It returns undefined. However from my understanding this should be returning what the selector is, which in this case should be: '{'header': ['value1', 'value2']}'
Am I missing something?
edit: For reference I am testing against a function that does this:
function parseStuff(str) {
return JSON.parse(
str
.substring(1, (str.length - 1))
.replace(/'/g, '"')
);
}
but the thing is, whats getting passed to is in controller/container (My Rails knowledge is limited so im going on what I can read). but for example some of the code looks like:
<instance of container/controller>.data('tableValues')
where 'tableValues is defined as a selector earlier on in the code, and the instance of container/controller is probably the table object itself I'd assume:
tableValues: 'table[data-table-options]' //A bunch more of these selectors are defined
So essentially im trying to set up a HTML fixture I can test this against. The HTML I posted was how it appears in chrome dev tools (obviously with a lot more stuff in the table element, but im just focusing on this selector in particular)
Retrieving data-* value
You refer to the attribute as table[data-table-values], which is incorrect on two accounts:
table[] is not necessary. I'm not sure why you were using that. This isn't a selector, this is a property lookup.
data-table-values should be table-values. Your HTML attribute is called "data-table-values", but the .data() lookup does not need that for any of the HTML data-* attributes.
The end result of both of these is to have .data('table-values') as evidenced in the example at the bottom.
If you wanted to look up the actual attribute value, then you would need to include the data- prefix, because you're searching for that specific HTML attribute; e.g., $test.attr('data-table-values'), but it's against common practice to do this for data-* attributes.
One thing you should note about the .data() method is that, it operates in memory. It doesn't update your HTML. If you use any sort of inspector you would see that setting $test.data('foo','bar'), it does not bind a foo attribute to the element, nor modifies the DOM, it manages the value internally to jQuery.
Fixing JSON
To use the value as JSON, you need to store it as wellformed JSON, right now you're using single quotes as the encapsulating JSON string delimiter '{'':[]}' and also to delimit the object keys. You must use double quotes for valid JSON.
In the example below, I trim off your leading and ending single quotes (') and then replace all the single quotes around the keys with double quotes ". I broke this into several lines for clarity, but could be condensed into several lines.
Note: if the JSON is valid and properly stored in the data-* attribute, then the client wouldn't need to do so much work scrubbing it and the code would be much more simpler
var $test = $('#test');
var str = $test.data('table-values'); // <=== was:"table[data-table-values]"
console.log('data-* value:', str);
// format to a valid JSON string
str = str.replace(/^'+/, ''); // remove leading single quote
str = str.replace(/'+$/, ''); // remove ending single quote
str = str.replace(/'/g, '"'); // use double quotes
console.log('Replaced single quotes:', str);
// make JS object
var obj = JSON.parse(str);
console.log('obj:', obj);
console.log('obj.header[1]:', obj.header[1]);
// make JSON string
console.log('JSON:', JSON.stringify(obj));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="test" data-table-values="'{'header': ['value1', 'value2']}'"></table>
Better HTML
Here is an example per #guest271314's suggestion, which demonstrates having well-formed JSON in your HTML.
Notice there are far fewer lines (only one required to get the JSON object). Provided is the jQuery and the ES6 equivalent:
// jQuery
var obj = $('#test').data('table-values');
console.log('obj:', obj);
console.log('obj.header[1]:', obj.header[1]);
console.log('JSON.stringify(obj):', JSON.stringify(obj));
// ES6
var obj2 = JSON.parse(document.querySelector('#test').dataset['tableValues']);
console.log('obj2:', obj2);
console.log('obj2.header[1]:', obj2.header[1]);
console.log('JSON.stringify(obj2):', JSON.stringify(obj2));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="test" data-table-values='{"header": ["value1", "value2"]}'></table>
The issue is because you need to provide the attribute name, minus the data- prefix, to the data() method, not the element selector. Try this:
var $test = $('#test');
console.log($test.data('table-values'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="test" data-table-values="'{'header': ['value1', 'value2']}'"></table>
Also note that if you provide the attribute as HTML encoded JSON then jQuery will automatically deserialise it for you so that you can immediately work with the object's properties:
var $test = $('#test');
var obj = $test.data('table-values')
console.log(obj);
console.log(obj.header[0]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="test" data-table-values="{"header": ["value1","value2"]}"></table>

Why is my .data() function returning [ instead of the first value of my array?

I want to pass an array into a jQuery data attribute on the server side and then retrieve it like so:
var stuff = $('div').data('stuff');
alert(stuff[0]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<div data-stuff="['a','b','c']"></div>
Why does this appear to alert '[' and not 'a' (see JSFiddle link)
JSFiddle Link: http://jsfiddle.net/ktw4v/3/
It's treating your variable as a string, the zeroth element of which is [.
This is happening because your string is not valid JSON, which should use double-quotes as a string delimiter instead of single quotes. You'll then have to use single-quotes to delimit the entire attribute value.
If you fix your quotation marks your original code works (see http://jsfiddle.net/ktw4v/12/)
<div data-stuff='["a","b","c"]'> </div>
var stuff = $('div').data('stuff');
When jQuery sees valid JSON in a data attribute it will automatically unpack it for you.
Declaring it as an attribute means that it is a string.
So stuff[0] would be equivalent to: var myString = "['a','b','c']"; alert(myString[0]);
You need to make it look like this:
<div data-stuff="a,b,c"></div>
var stuff = $('div').data('stuff').split(',');
alert(stuff[0]);
Retraction: jQuery's parsing fails because it didn't meet the rules of parseJSON.
However, I will stand behind my solution. There are aspects of the others that are less than ideal, just as this solution is less than ideal in some ways. All depends on what your paradigms are.
As others have identified the value is treated as string so it is returning "[". Please try this (aaa is the name of the div and I took out the data-stuff):
$(function(){
$.data($("#aaa")[0],"stuff",{"aa":['a','b','c']});
var stuff = $.data($("#aaa")[0],"stuff").aa;
alert(stuff[0]); //returns "a"
});
A different approach is posted at jsfiddle; var stuff = $('div').data('stuff'); stuff is a string with 0th character as '['
Well, var stuff = eval($('div').data('stuff')); should get you an array

convert "converted" object string to JSON or Object

i've following problem and since i upgraded my prototypeJS framework.
the JSON parse is not able anymore to convert this string to an object.
"{empty: false, ip: true}"
previously in version 1.6 it was possible and now it needs to be a "validated" JSON string like
'{"empty": false, "ip": true}'
But how can i convert the 1st example back to an object?
JSON needs all keys to be quoted, so this:
"{empty: false, ip: true}"
is not a valid JSON. You need to preprocess it in order to be able to parse this JSON.
function preprocessJSON(str) {
return str.replace(/("(\\.|[^"])*"|'(\\.|[^'])*')|(\w+)\s*:/g,
function(all, string, strDouble, strSingle, jsonLabel) {
if (jsonLabel) {
return '"' + jsonLabel + '": ';
}
return all;
});
}
(Try on JSFiddle) It uses a simple regular expression to replace a word, followed by colon, with that word quoted inside double quotes. The regular expression will not quote the label inside other strings.
Then you can safely
data = JSON.parse(preprocessJSON(json));
It makes sense that the json parser didn't accept the first input as it is invalid json. What you are using in the first example is javascript object notation. It's possible to convert this to an object using the eval() function.
var str = "({empty: false, ip: true})";
var obj = eval(str);
You should of course only do this if you have the guarantees the code you'll be executing is save.
You can find more information about the json spec here. A json validator can be found here.
edit: Thai's answer above is probably a better solution
const dataWithQuotes = str.replace(/("(\\.|[^"])*"|'(\\.|[^'])*')|(\w+)\s*:/g, (all, string, strDouble, strSingle, jsonLabel) => {
if (jsonLabel) {
return `"${jsonLabel}": `;
}
return all;
});
return dataWithQuotes
similar solution as above , but updated with arrow functions.

problem with iterating through the JSON object

In the code below, I tried iterating over the JSON object string. However, I do not get the desired output. My output on the webpage looks something like:-
+item.temperature++item.temperature++item.temperature++item.temperature+
The alert that outputs the temperature works well. The part where I try accessing the value by iterating through the JSON object string doesn't seem to work. Could some one help me out with fixing this?
Code
<body>
<script>
$.getJSON('http://ws.geonames.org/weatherJSON?north=90&south=-9.9&east=-22.4&west=55.2',
function(data) {
$.each(data.weatherObservations, function(i, item) {
$("body").append("+item.temperature+");
if (-i == 3-) return false;
});
alert(data.weatherObservations[0].temperature);
});
</script>
</body>
Don't use quotes within $("body").append("+item.temperature+"); in the .append() part.
should be
$(document.body).append(item.temperature);
Writting that expression with quotes like you did, just adds a string over and over. Java//Ecmascript interpretates anything withing quotes as a string literal.
Notice that I also replaced "body" with document.body. That has mostly performance // access reasons, so better karma.
Your code is iterating through, but you're appending "+item.temperature+", don't you want to do something like
$("body").append("Temp is " + item.temperature);
or
$("body").append(item.temperature);
"+item.temperature+" means a string which is "+item.temperature+"
"pre" + item.temperature + "post" will concatenate the variable to the strings.
$.getJSON('http://ws.geonames.org/weatherJSON?north=90&south=-9.9&east=-22.4&west=55.2',
function (data) {
$.each(data.weatherObservations, function (i, item) {
$("body").append(item.temperature + ",");
if (i == 3) return false;
});
alert(data.weatherObservations[0].temperature);
});

Categories