Note: I changed the title of the question, as well as all reference to JSON so that the question better reflects my problem. I got several times the advice of "better iterate the object than work on a serialized version" but I believe (and am certainly mistaken and wrong) that searching for a well-defined pattern in a string is easier than go for iterative or recursive code to iterate an object
I need to extract the pattern "something":"thestring" from a string.
The source string will have many other combinations such as "something":[{"thestring":{"key":18,"anotherkey":"astring"}}], from which only the pair "anotherkey":"astring" is sought for.
I am specifically interested in getting the content of the value, that is thestring in the first example (and astring in the second one).
I tried to match ".*?","(.*?)" but I get more than just the pair, matching the comma after the quote (and it goes downhill form there).
An example of a test string and my failed test is on Regex101
Here is how I would write this:
function extractStrings(obj) {
var stringSet = [];
function extractStringsHelper(obj) {
if (typeof obj === 'string' && stringSet.indexOf(obj) === -1) {
stringSet.push(obj);
} else if (typeof obj === 'array') {
for (var i=0; i<obj.length; i++) {
extractStringsHelper(obj);
}
} else if (typeof obj === 'object' && obj !== null) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
extractStringsHelper(obj[key]);
}
}
}
}
extractStringsHelper(obj);
return stringSet;
}
> extractStrings({'a': "strA", 'b': ["strB1", 1, "strB2", {'n': "strC"}]})
> (4) ["strA", "strB1", "strB2", "strC"]
You can also go the regex route and look for:
"[^"]+":"([^"]+)"
Here is your example with the modified regex: https://regex101.com/r/uxS9k0/2
But this path is dark and full of terrors. For example, it breaks if the string contains an escaped double quote. Once you start accounting for all the possible cases, you are basically rewriting a JSON tokenizer.
Try this regex :
If you really want to work on a string, this regex will do what you want, as long as what you look for is always between quotes and preceded by a key between quotes :
"\w+":"(\w+)"
Demo here
The value will be captured in group 1
Here is how to get your value :
var regex = /"\w+":"(\w+)"/g;
var json = "\"something\":\"thestring\"\n\"something\":[{\n\t\"thestring\":{\n\t\t\"key\":18,\n\t\t\"anotherkey\":\"astring\"\n\t}\n}]";
console.log(json);
var match = regex.exec(json);
for (i = 1; match != null; i++) { // You need to loop until you match every value
// Full match is in match[0]
// Your value is in match[1]
console.log("Val"+i+": "+match[1])
match = regex.exec(json);
}
If you are working with valid json you shouldn't need to use regex, and can deserialize it with the following:
var data = JSON.parse(json_string);
Related
I am new to JavaScript. I have created a indexof function in but it is not giving the correct output:
Question is:
/*
Implement a function called indexOf that accepts two parameters: a string and a character, and returns the first index of character in the string.
*/
This is my code:
function indexOf(string, character) {
let result = string;
let i = 0;
let output = 1;
while (i < result.length) {
if (result[i] === character) {
output = output + indexOf[i];
}
}
return output;
}
I want to know what i am doing wrong. Please Help.
You are making things a little harder than you need to. If you want to do this without calling the built-in indexOf(), which I assume is the point of the exercise, you just need to return from the function as soon as your condition matches. The instructions say "return the first index" — that's the i in your loop.
If you make it through the loop without finding something it's traditional to return -1:
function indexOf(string, character) {
let i=0;
while(i < string.length){
if(string[i] == character){ // yes? just return the index i
return i
}
i++ // no? increase i and move on to next loop iteration
}
return -1; // made it through the loop and without returning. This means no match was found.
}
console.log(indexOf("Mark Was Here", "M"))
console.log(indexOf("Mark Was Here", "W"))
console.log(indexOf("Mark Was Here", "X"))
Assuming from your question that the exercise is to only match the first occurrence of a character and not a substring (multiple characters in a row), then the most direct way to do it is the following:
const indexOf = (word, character) => {
for (let i = 0; i < word.length; i++) {
if (word[i] === character) {
return i;
}
}
return -1;
}
If you also need to match substrings, leave a comment on this answer if you can't figure it out and I'll help you along.
indexOf() is a built in method for strings that tells you the index of a particular character in a word. Note that this will always return the index of the FIRST matching character.-
You can write something like:
function indexOf(string, character){
return string.indexOf(character)
}
So if I were to use my function and pass in the two required arguments:
indexOf("woof", "o") //this would return 1
I have this json
var json = {
"I:5.3": {
"conf": {
"name": "Jack"
}
}
}
and this string
var str = '["I.5.3"]["conf"]'
How can get str value from json like
json["I.5.3"]["conf"]
I have json object and str and I have to extract something like
json[str] = json["I.5.3"]["conf"]
Your title suggests you would like to get:
The value of a string.
Which has a list of Array's.
From a JSON object.
However, all your code shows is a Plain JavaScript object. No arrays to be seen... and the only string value I can find is "Jack".
Jack would be extracted like, json["I.5.3"]["conf"]["name"].
Or via dot notation =>
const obj = json["I.5.3"];
const nameValue = obj.conf.name; // => Jack
You have 2 ways(at least that's what's in coming through my mind now):
1st way
var json = {
"I:5.3": {
"conf": {
"name": "Jack"
}
}
}
var str = '["I:5.3"]["conf"]'
var scr_line = 'json'+str;
var target = eval(scr_line);
console.log(target);
2nd way:
var json = {
"I:5.3": {
"conf": {
"name": "Jack"
}
}
}
var str = '["I:5.3"]["conf"]';
let ret = getVal(json, str);
console.log(ret);
function getVal(obj, path){
var regex = /\["(.*?)"\]/mg;
let m;
while ((m = regex.exec(path)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
if(typeof obj[m[1]] !== 'undefined') obj = obj[m[1]];
else return obj[m[1]];
}
return obj;
}
I would prefer the second because it checks if the property on the object exists
You would have to split the string up into separate parts, I believe.
var str = '["I.5.3"]["conf"]';
var identifiers = str.split('][');
// identifiers would be an array like:
// ["I.5.3", "conf"]
var person = json[identifiers[0]][identifiers[1]];
// person = {
// "name": "Jack"
// }
String.prototype.split() allows you to separate parts of a string out into an array. Checkout the MDN docs to learn more about that particular method.
This specific answer expects that there will always be the properties you split, though and will error out if one is missing. For something a little safer, I would suggest checking to see if that object contains the property you're expecting before trying to access it.
var str = '["I.5.3"]["conf"]';
var identifiers = str.split('][');
try {
var myVal;
// Let's check if the property exists.
if (Object.prototype.hasOwnProperty.call(json, identifiers[0]){
myVal = json[identifiers[0]];
if (Object.prototype.hasOwnProperty.call(myVal, identifiers[1]){
myVal = myVal[identifiers[1]];
}
}
} catch(error) {
// One of the properties didn't exist or something went wrong in the try
// block above.
}
EDIT:
The follow would format your string specifically to meet the match to become an array. If there were single quotes or double quotes anywhere in each array item, this would fall through, so be aware of that.
var myArray = str.slice(2, -2).split('][').map(function(item) {
return item.replace('"', '');
});
String.prototype.slice() extracts a section of a string and returns it as a new string, without modifying the original string.
Then the split method separates it into different array items. Then we iterate over each item in the array and remove additional ". Just to say again, this will fall apart if the original string looks like ['one']['two']. This would also not be reliable if the string looks like ["can't"]["won't"]. So just be aware of that in your particular case. If you're positive that the string will always meet the format you have above, then you can rely on this.
I've been trying for a couple hours to do this, most questions and examples I've seen are not addressing my problem, for instance This one here is talking about the keys, not values.
I tired using the JSON parser, but there are two issues:
How do I iterate through the values to begin with? I know there are different ways to read keys, but what about values and nested values (given that we don't know anything about what the keys or values are called).
How to actually write the value and replace it, rather than replacing the whole file, maybe something like:
key.value=key.value.toUpper();
I am looking for a solution that works for any JSON file, with absolultly no knowledge what the keys are called.
You could use replace to operate on the JSON string directly:
jsonString.replace(/"\s*:\s*"[^"]/g, match => {
return match.slice(0, -1) + match[match.length - 1].toUpperCase()
})
That would save you from having to parse the JSON, and might be a bit faster. It can be tough to write a performant comprehensive RegEx though, so it might be safer just to parse the JSON and write a little recursive function:
const uppercaseValues = obj => {
return Object.keys(obj).reduce((uppercased, key) => {
const value = obj[key]
if (typeof value === 'string') {
uppercased[key] = value[0].toUpperCase() + value.slice(1)
} else if (typeof value === 'object') {
uppercased[key] = uppercaseValues(value)
} else {
uppercased[key] = value
}
return uppercased
}, {})
}
const parsedJson = JSON.parse(jsonString)
const uppercased = uppercaseValues(parsedJson)
const xformedJson = JSON.stringify(uppercased)
I've written a basic function to count word frequency within a string. First, I split the string into an array, then I iterate through the array of words via for loop. That said, if initial string is empty (""), my function will regard "" as a word and thus the result is {"": 1} rather than an empty object. I initially thought that the empty string wouldn't register at all, due to the for-loop.
I've fixed this issue through an if conditional at the start of the function, but I was wondering if there was a better way to go about it.
function countWords(str) {
if (str === "") {
return {};
}
var counts = {};
var wordArray = str.split(" ");
for (i=0;i < wordArray.length; i++) {
word = wordArray[i];
if (!counts[word]) {
counts[word] = 1;
} else {
counts[word] += 1;
}
}
return counts;
}
My Ruby code equivalent did not require anything to not pick up the empty string as a viable word target, so this bothers me somewhat.
Edit: Thank you to all the responses. I appreciate all the help.
Depends on the situation, but if in your case only strings are passed as parameter that check is good enough.
If there's possibility of undefined, null or other falsy value being passed as a parameter as well, you could do this check instead:
if (!str) {
return {};
}
If you want a check even stricter you could do:
if (!str || typeof str !== 'string') {
return {};
}
In this case you are checking falsy values(as we saw above empty string is a falsy value) and making sure you are getting as parameter a string.
This is expected behavior. .split() does not guarantee non-empty strings in the results. Unless you filter out empty strings explicitly, all empty strings that .split() returns will be counted.
Since .split does not coalesce sequential split strings, you may have a lot of empty strings after split: try "a b c".split(" ").
I currently have the following function, which works:
String.prototype.matchAny = function(patterns) {
var that = this;
var matched = false;
// Iterate patterns and return key of first match
$.each(patterns, function(i, v) {
if (that.match(v)) {
matched = i;
return false;
}
});
return matched;
};
It will return the index in patterns of the first matched regular expression for the string. For example:
"blah".matchAny([/a/, /z/]); // 0
"blah".matchAny({z: /z/, a: /a/]); // "a"
However, this solution feels clunky to me. I've had some success using Array.some(), and while it's a lot more elegant, I can't find a way to get the result I want (return the key from patterns that matched).
Is there something I'm missing? More specifically, is there a built-in function (JavaScript/jQuery) that can produce the same results?