string containing equals "=" and no quotes convert to JSON - javascript

Im currently stuck trying to convert a string into JSON in javascript.
the string im getting from the server is:
"{knee=true, centered=true}"
the outcome im looking for is something like this:
{ knee: true, centered: true}
but since the string is using equals and there are missing quotes the JSON.parse isnt working, I dont know how to solve this. any help will be appreciated, thank you!

The best I could do was this ... It returns value of object in strings though it seems to work perfect ! ( Actually this one challenged me so I had to do it ) :-)
let str = "{knee = true, centered = true}";
str = str.replaceAll('{', '')
str = str.replaceAll('}', '')
str = str.split(",")
str = Object.assign({}, str);
let key_value;
let key;
let val;
for (var i = 0; i < Object.keys(str).length; i++) {
key_value = str[i].split("=");
key = String(key_value[0]);
val = key_value[1];
str[i] = val;
delete Object.assign(str, {[key]: str[i]
})[i];
}
console.log(str)

Assuming you don't have nested things or strings with commas or brackets in them, you could replace all { with {", = with ":, and , with , ":
const str = "{knee=true, centered=true}"
console.log(
JSON.parse(str.split('{').join('{"').split('=').join('":').split(', ').join(', "'))
)

Without more specifics it's impossible to verify how correct this is, but if I was to make some assumptions:
An object is a set of key/value pairs surrounded by { and }
Key/value pairs are separated by ,
Any arbitrary whitespace is allowed around key/value pairs
A key and value are separated by a =
Values can only hold the value true or false which should be translated to a JavaScript boolean
...then parsing can be done through some regular expressions and string manipulations.
const objectRegExp = /^\{(.*)}$/;
function parseNJson(str) { // notJSON
const match = objectRegExp.exec(str);
if (!match) {
throw new Error('This is not NJson');
}
const [, keyValuesBlock] = match;
const keyValueStatements = keyValuesBlock.split(',');
const keyValues = keyValueStatements.map(statement => statement.split('='));
return keyValues.reduce((result, [keyStr, valueStr]) => {
const key = keyStr.trim();
const trimmedValue = valueStr.trim();
let value;
if (trimmedValue === 'true') {
value = true;
} else if (trimmedValue === 'false') {
value = false;
} else {
throw new Error(`Unsupported value ${trimmedValue}`);
}
return Object.assign(result, { [key]: value });
}, {});
}
This will easily fall apart if any assumptions were incorrect, like "what if values can be strings? What if strings can be quoted with double quotes? What if they can also be surrounded by single quotes? What if numbers are supported? What if hexadecimal numbers are supported?"
If the data being sent on the server is a standard format, they should be able to tell you "this was formatted as X" so you can find a spec-compliant X parser. Or you could insist data is sent as JSON instead, since that's a super common exchange format. The best thing is that the server and client are using a common, well-defined message formatting spec so you don't accidentally break things whenever receiving or sending data that has characteristics you didn't account for.

Related

Uncompress a String, repeat chars `n` times

I have the following problem statement:
Write a function, uncompress, that takes in a string as an argument.
The input string will be formatted into multiple groups according to
the following pattern:
number + char
for example, '2c' or '3a'.
The function should return an uncompressed version of the string where
each 'char' of a group is repeated 'number' times consecutively. You
may assume that the input string is well-formed according to the
previously mentioned pattern.
test_00: uncompress("2c3a1t"); // -> 'ccaaat'
Here is my code which is using a stack. The problem is that it's only returning 'cc' and I can't figure out why. I've console logged what goes into the IF ELSE and I'm hitting both so I don't understand why nothing gets pushed to the stack.
Would really appreciate the help if someone can spot what I'm missing.
const uncompress = (s) => {
const nums = '23456789';
const stack = [];
for (let char of s) {
if (nums.includes(char)) {
stack.push(Number(char));
} else {
const num = stack.pop();
stack.push(char.repeat(num));
};
};
return stack.join('');
};
console.log(uncompress("2c3a1t")); // -> 'ccaaat'
Here's how I would do it:
Split the string up into pairs of numbers and chars:
str.match(/\d+[a-zA-Z]/g)
And reduce that array to a string, while taking each value from the array, getting the char from it (cv.match(/[a-zA-Z]/)[0]) and repeating it according to the number (.repeat(parseInt(cv)))
const uncompress = str => str.match(/\d+[a-zA-Z]/g).reduce((acc, cv) =>
acc + cv.match(/[a-zA-Z]/)[0].repeat(parseInt(cv)), "")
console.log(uncompress("2c3a1t"))
console.log(uncompress("27b1d8g"))
And just like that I was able to write the code which passed the test case:
const nums = '123456789';
const stack = [];
for (let char of s) {
if (nums.includes(char)) {
stack.push(Number(char));
} else {
let num = '';
while (nums.includes(stack[stack.length - 1])) {
num += stack.pop();
}
stack.push(char.repeat(num));
};
};
return stack.join('');
};

Convert string with '=' to JSON format

I am trying to convert a string i receive back from an API into a JSON object in Angular.
The issue is that the string is not normalized to be parsed into JSON easily.
This is the string im working with:
"{rootCause=EJBusinessException: This is a sample exception thrown for testing additional info field, description=This is a more detailed description about the incident., stackTrace=com.springboot.streams.infrastructure.web.heartbeat.HeartbeatService.testServiceNow(HeartbeatService.java:200)}"
When trying to do JSON.parse(myStr) it throws an error due to invalid string format.
Is there an easy way to convert the listed string into a more correct JSON format, getting rid of the '=' and replacing them with ':' instead.
There is more to it than just .replace(/['"]+/g, ''), as even with that the string is not ready to be turned into JSON yet.
Hoping someone more versed in Javascript knows a trick i dont.
You just need to manipulate the string before parsing it remove unecessary string that can cause error to the object like "{" and "}" and split it by "," example is in below.
var obj = {}, str = "{rootCause=EJBusinessException: This is a sample exception thrown for testing additional info field, description=This is a more detailed description about the incident., stackTrace=com.springboot.streams.infrastructure.web.heartbeat.HeartbeatService.testServiceNow(HeartbeatService.java:200)}"
str.split(",").forEach((st, i) => {
pair = st.split("=")
if(pair.length > 1) {
obj[pair[0].replace("{",'').replace("}", '').trim()] = pair[1]
} else {
obj[i] = pair
}
})
console.log(obj)
As commenters have posted, unless you control the API or at least have documentation that output will always follow a specific format, then you are limited in what you can do. With your current example, however you can trim off the extraneous bits to get the actual data... (remove braces, split on comma, split on equals) to get your key:value pairs... then build a javascript object from scratch with the data... if you need json string at that point can just JSON.stringify()
var initialString = "{rootCause=EJBusinessException: This is a sample exception thrown for testing additional info field, description=This is a more detailed description about the incident., stackTrace=com.springboot.streams.infrastructure.web.heartbeat.HeartbeatService.testServiceNow(HeartbeatService.java:200)}"
var trimmedString = initialString.substr(1, initialString.length - 2);
var pairArray = trimmedString.split(',');
var objArray = [];
pairArray.forEach(pair => {
var elementArray = pair.split('=');
var obj = {
key: elementArray[0].trim(),
value: elementArray[1].trim()
};
objArray.push(obj);
});
var returnObj = {};
objArray.forEach(element => {
returnObj[element.key] = element.value;
});
console.log(JSON.stringify(returnObj));

.toLowerCase() / .toUpperCase() not working

I'm trying to get better at javascript through codewars.com katas, and I came across an exercice in which things like element[i]=element[i].toLowerCase() doesn't change anything at all.
I would like to have some help with my code, here is the exercice's instructions followed by my code:
(Please note that I'm not very experienced with JS so the code may not be perfect at all)
A string is considered to be in title case if each word in the string
is either:
(a) capitalised (that is, only the first letter of the word
is in upper case) or
(b) considered to be an exception and put entirely into lower case unless it is the first word, which is always capitalised.
Write a function that will convert a string into title case, given an optional list of exceptions (minor words). The list of minor words will be given as a string with each word separated by a space.
Your function should ignore the case of the minor words string -- it should behave in the same way even if the case of the minor word string is changed.
Arguments:
First argument (required): the original string to be converted.
Second argument (optional): space-delimited list of minor words that must always be lowercase except for the first word in the string. The JavaScript/CoffeeScript tests will pass undefined when this argument is unused.
function titleCase(title, minorWords) {
if(title.length==0){return ""}
var titlesplit = title.split(" ")
if(minorWords){
minorWords=minorWords.split(" ")
}
var solutionstring = ""
titlesplit.forEach(element => myfunction(element,minorWords))
solutionstring[0] = solutionstring[0].toUpperCase()
return solutionstring
function myfunction(element,minorWords){
var elementlength= element.length
var i=0
if(minorWords && minorWords.includes(element)){
for(i;i<elementlength;i++){
element[i]=element[i].toLowerCase()
}
}else {
for(i;i<elementlength;i++){
if(i==0){element[i]=element[i].toUpperCase()}
else{element[i]=element[i].toLowerCase()}
}
}
if(solutionstring.length==0){solutionstring=solutionstring+element}else{solutionstring=solutionstring+" "+element}
return
}
}
As pointed out in comments, Strings are immutable in JavaScript.
Additionally, for searching use Maps instead of includes.
Likewise you can see what Set in JavaScript is and easily use Set here.
Added comments for you better understanding.
function titleCase(title, minorWords) {
// Use === for comparison
// Prefer using curly braces even for single statements
if (title.length === 0) {
return "";
}
var titlesplit = title.split(" ");
// Maps/Objects give O(1) search compared to arrays O(n)
// Key,value pairs - similar to dictionary
var minorWordsMap = {};
minorWords.split(" ").forEach(i => minorWordsMap[i.toLowerCase()] = true);
var finalWords = titlesplit.map((element, index) => convertCase(element, index));
finalWords[0] = toPascalCase(finalWords[0]);
return finalWords.join(" ");
function toPascalCase(s) {
s = s.split("");
s[0] = s[0].toUpperCase();
return s.join("");
}
function convertCase(element, index) {
const lElement = element.toLowerCase();
// If element is part of exception words, ignore
if(index !== 0 && minorWordsMap[lElement]) {
return element;
}
// If first element or not in exception list, send Title case
return toPascalCase(lElement);
}
}

How do I parse JSON sprinkled unpredictably into a string?

Suppose that I've got a node.js application that receives input in a weird format: strings with JSON arbitrarily sprinkled into them, like so:
This is a string {"with":"json","in":"it"} followed by more text {"and":{"some":["more","json"]}} and more text
I have a couple guarantees about this input text:
The bits of literal text in between the JSON objects are always free from curly braces.
The top level JSON objects shoved into the text are always object literals, never arrays.
My goal is to split this into an array, with the literal text left alone and the JSON parsed out, like this:
[
"This is a string ",
{"with":"json","in":"it"},
" followed by more text ",
{"and":{"some":["more","json"]}},
" and more text"
]
So far I've written a naive solution that simply counts curly braces to decide where the JSON starts and stops. But this wouldn't work if the JSON contains strings with curly braces in them {"like":"this one } right here"}. I could try to get around that by doing similar quote counting math, but then I also have to account for escaped quotes. At that point it feels like I'm redoing way too much of JSON.parse's job. Is there a better way to solve this problem?
You can check if JSON.parse throws an error to determine if the chunk is a valid JSON object or not. If it throws an error then the unquoted } are unbalanced:
const tests = [
'{"just":"json }}{}{}{{[]}}}}","x":[1,2,3]}',
'Just a string',
'This string has a tricky case: {"like":"this one } right here"}',
'This string {} has a tiny JSON object in it.',
'.{}.',
'This is a string {"with":"json","in":"it"} followed by more text {"and":{"some":["more","json"]}} and more text',
];
tests.forEach( test => console.log( parse_json_interleaved_string( test ) ) );
function parse_json_interleaved_string ( str ) {
const chunks = [ ];
let last_json_end_index = -1;
let json_index = str.indexOf( '{', last_json_end_index + 1 );
for ( ; json_index !== -1; json_index = str.indexOf( '{', last_json_end_index + 1 ) ) {
// Push the plain string before the JSON
if ( json_index !== last_json_end_index + 1 )
chunks.push( str.substring( last_json_end_index, json_index ) );
let json_end_index = str.indexOf( '}', json_index + 1 );
// Find the end of the JSON
while ( true ) {
try {
JSON.parse( str.substring( json_index, json_end_index + 1 ) );
break;
} catch ( e ) {
json_end_index = str.indexOf( '}', json_end_index + 1 );
if ( json_end_index === -1 )
throw new Error( 'Unterminated JSON object in string' );
}
}
// Push JSON
chunks.push( str.substring( json_index, json_end_index + 1 ) );
last_json_end_index = json_end_index + 1;
}
// Push final plain string if any
if ( last_json_end_index === - 1 )
chunks.push( str );
else if ( str.length !== last_json_end_index )
chunks.push( str.substr( last_json_end_index ) );
return chunks;
}
Here's a comparatively simple brute-force approach: split the whole input string on curly braces, then step through the array in order. Whenever you come across an open brace, find the longest chunk of the array from that starting point that successfully parses as JSON. Rinse and repeat.
This will not work if the input contains invalid JSON and/or unbalanced braces (see the last two test cases below.)
const tryJSON = input => {
try {
return JSON.parse(input);
} catch (e) {
return false;
}
}
const parse = input => {
let output = [];
let chunks = input.split(/([{}])/);
for (let i = 0; i < chunks.length; i++) {
if (chunks[i] === '{') {
// found some possible JSON; start at the last } and backtrack until it works.
for (let j = chunks.lastIndexOf('}'); j > i; j--) {
if (chunks[j] === '}') {
// Does it blend?
let parsed = tryJSON(chunks.slice(i, j + 1).join(""))
if (parsed) {
// it does! Grab the whole thing and skip ahead
output.push(parsed);
i = j;
}
}
}
} else if (chunks[i]) {
// neither JSON nor empty
output.push(chunks[i])
}
}
console.log(output)
return output
}
parse(`{"foo": "bar"}`)
parse(`test{"foo": "b}ar{{[[[{}}}}{}{}}"}`)
parse(`this {"is": "a st}ri{ng"} with {"json": ["in", "i{t"]}`)
parse(`{}`)
parse(`this {"i{s": invalid}`)
parse(`So is {this: "one"}`)
I could try to get around that by doing similar quote counting math, but then I also have to account for escaped quotes. At that point it feels like I'm redoing way too much of JSON.parse's job. Is there a better way to solve this problem?
I don't think so. Your input is pretty far from JSON.
But accounting for all those things isn't that hard.
The following snippet should work:
function construct(str) {
const len = str.length
let lastSavedIndex = -1
let bracketLevel = 0
let inJsonString = false
let lastCharWasEscapeChar = false
let result = []
for(let i = 0; i < len; ++i) {
if(bracketLevel !== 0 && !lastCharWasEscapeChar && str[i] === '"') {
inJsonString = !inJsonString
}
else if (!inJsonString && str[i] === '{') {
if (bracketLevel === 0) {
result.push(str.substring(lastSavedIndex + 1, i))
lastSavedIndex = i - 1
}
++bracketLevel
}
else if (!inJsonString && str[i] === '}') {
--bracketLevel
if (bracketLevel === 0) {
result.push(JSON.parse(str.substring(lastSavedIndex + 1, i + 1)))
lastSavedIndex = i
}
}
else if (inJsonString && str[i] === '\\') {
lastCharWasEscapeChar = !lastCharWasEscapeChar
}
else {
lastCharWasEscapeChar = false
}
}
if(lastSavedIndex !== len -1) {
result.push(str.substring(lastSavedIndex + 1, len))
}
return result
}
const standardText = 'This is a string {"with":"json","in":"it"} followed by more text {"and":{"some":["more","json"]}} and more text. {"foo": "bar}"}'
const inputTA = document.getElementById('input')
const outputDiv = document.getElementById('output')
function updateOutput() {
outputDiv.innerText =
JSON.stringify(
construct(inputTA.value),
null,
2
)
}
inputTA.oninput = updateOutput
inputTA.value = standardText
updateOutput()
<textarea id="input" rows="5" cols="50"></textarea>
<pre id="output"><pre>
You can use RegExp /(\s(?=[{]))|\s(?=[\w\s]+[{])/ig to .split() space character followed by opening curly brace { or space character followed by one or more word or space characters followed by opening curly brace, .filter() to remove undefined values from resulting array, create a new array, then while the resulting split array has .length get the index where the value contains only space characters, .splice() the beginning of the matched array to the index plus 1, if array .length is 0 .push() empty string '' else space character ' ' with match .join()ed by space character ' ' .replace() last space character and .shift() matched array, which is JSON, then next element of the matched array.
const str = `This is a string {"with":"json","in":"it"} followed by more text {"and":{"some":["more","json"]}} and more text {"like":"this one } right here"}`;
const formatStringContainingJSON = s => {
const r = /(\s(?=[{]))|\s(?=[\w\s]+[{])/ig;
const matches = s.split(r).filter(Boolean);
const res = [];
while (matches.length) {
const index = matches.findIndex(s => /^\s+$/.test(s));
const match = matches.splice(0, index + 1);
res.push(
`${!res.length ? '' : ' '}${match.join(' ').replace(/\s$/, '')}`
, `${matches.shift()}`
);
};
return res;
}
let result = formatStringContainingJSON(str);
console.log(result);
Here you one approach that iterates char by char. First we create an array from the input and then use reduce() on it. When we detect an opening curly bracket { we push the current accumulated chunk on an array of detected results, and then we set a flag on the accumulator object we are using on reduce. While this flag is set to true we will try to parse for a JSON and only when success we put the chunk representing the JSON on the array of detected results and set the flag again to false.
The accumulator of the reduce() method will hold next data:
res: an array with detected results: strings or jsons.
chunk: a string representing the current accumulated chunk of chars.
isJson: a boolean indicating if the current chunk is json or not.
const input = 'This is a string {"with":"json", "in":"it"} followed by more text {"and":{"some":["more","json","data"]}} and more text';
let obj = Array.from(input).reduce(({res, isJson, chunk}, curr) =>
{
if (curr === "{")
{
if (!isJson) res.push(chunk);
chunk = isJson ? chunk + curr : curr;
isJson = true;
}
else if (isJson)
{
try
{
chunk += curr;
JSON.parse(chunk);
// If no error, we found a JSON.
res.push(chunk);
chunk = "";
isJson = false;
}
catch(e) {/* Ignore error */}
}
else
{
chunk += curr;
}
return {res, isJson, chunk};
}, {res:[], isJson:false, chunk:""})
// First stage done, lets debug obtained data.
obj.res.push(obj.chunk);
console.log(obj.res);
// Finally, we map the pieces.
let res = obj.res.map(x => x.match("{") ? JSON.parse(x) : x);
console.log(res);
Obligatory answer: this is an improper format (because of this complication, and the guarantee is a security hole if the parser is improperly designed); it should ideally be redesigned. (Sorry, it had to be said.)
Barring that, you can generate a parser using your favorite parser generator that outputs to javascript as a target language. It might even have a demo grammar for JSON.
However, the glaring security issue is incredibly scary (if any JSON gets past the 'guarantee', suddenly it's a vector). An array interspersed representation seems nicer, with the constraint that assert(text.length == markup.length+1):
'{
"text": ['Hello', 'this is red text!'],
"markup": [{"text":"everyone", "color":"red"}]
}'
or even nicer:
'[
{"type":"text", "text":"Hello"},
{"type":"markup", "text":"everyone", "color":"red"} # or ,"val":{"text":.., "color":..}}
{"type":"text", "text":"this is red text!"},
...
]'
Store compressed ideally. Unserialize without any worries with JSON.parse.

Building JavaScript Array in C#, apostrophes changing

I have done this to build JavaScript Arrays from int, double and string lists.
public string listToJsArray<T>(List<T> cslist)
{
bool numeric = true;
if(
!(typeof(T)==typeof(int)
|| typeof(T) == typeof(string)
|| typeof(T) == typeof(double))
)
{
throw (new ArgumentException(message: "Only int, double and string are supported"));
}
if(typeof(T)==typeof(string))
{
numeric = false;
}
string JsArray = "[";
for(int i=0;i<cslist.Count;i++)
{
string dataWithSurrendings = cslist[i].ToString();
if(!numeric)
{
dataWithSurrendings = "'" + cslist[i].ToString() + "'";
}
if(i !=0)
{
dataWithSurrendings = "," + dataWithSurrendings;
}
if(i +1==cslist.Count)
{
dataWithSurrendings = dataWithSurrendings + "]";
}
JsArray += dataWithSurrendings;
}
return JsArray;
}
My problem is when a list of strings is passed, apostrophes turn into '.
for example, a list of {"1","2","3","4","5","6","7"} becomes this:
['1','2','3','4','1','6','7']
What modification is needed in this function, to return a correct array in JavaScript?
None of solutions did solve the problem. With JsonConvert I get almost same result. The problem is the single or double quote in View editor have not the same encoding as CS string.
I'm assuming that you are doing this to drop into a webpage somewhere, something like:
<script>
#{
var output = listToJsArray(Model.SomeList);
}
var myArray = #Html.Raw(output);
// some Javascript using that array
</script>
Don't waste your time trying to do it yourself. It's a pain and you are reinventing the wheel. JSON is valid Javascript and a serialization of an array into JSON is absolutely identical to a Javascript array literal. So use Javascript. JSON.Net is really useful here:
<script>
#{
var output = Newtonsoft.Json.JsonConvert.SerializeObject(Model.SomeList);
}
var myArray = #Html.Raw(output);
// some Javascript using that array
</script>
The serializer will handle all the annoying escaping, special characters and edge cases for you.

Categories

Resources