What is the alternative to the following Java code for JavaScript?
String[] strings = {"something written here", "five", "hello world", "this is a really big string", "two words"};
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (String str : strings)
{
bos.write(str.length());
bos.write(str.getBytes());
}
ByteBuffer buffer = ByteBuffer.wrap(bos.toByteArray());
// send buffer somewhere...
I know that I can read Java's ByteBuffer sent over the network with the following JS code, but couldn't figure out a way of writing a reply using JS.
let strings = [];
let bytes = Buffer.from(data);
while (bytes.length > 0) {
let size = bytes.readInt8(0) + 1;
strings.push(bytes.toString("UTF-8", 1, size));
bytes = bytes.slice(size);
}
Let's suppose I want to reply: ["hello", "how", "are you", "doing"]
The closest equivalent to a ByteArrayOutputStream would probably be a Writable stream that is backed by a growing array. I guess you'll have to implement this yourself though, see e.g. in Convert stream into buffer?.
It's probably simpler to directly append buffers to an array as they come in, then concat them all, to implement your growing buffer.
const strings = ["something written here", "five", "hello world", "this is a really big string", "two words"];
const output = [];
for (const str of strings) {
const bytes = Buffer.from(str, "utf-8");
output.push(Buffer.from([bytes.length+1]));
output.push(bytes);
}
const buffer = Buffer.concat(output);
Btw I'm pretty sure you want to use bytes.length, not str.length, for your length-prefixed strings? Same in your Java code, you need to use str.getBytes().length not str.length().
Highest Score Bergi solution is absolutely nice, but I don't get it why he has the code output.push(Buffer.from([bytes.length+1]));, and if I add it, it will go wrong. It will work when I remove it.
Related
Lets say I have a string like this
const str = `{FOO: "Hello\nWorld"}
{"BAR": "Nice\nTo\nMeet\nYou"}`
when I do str.split("\n")
the output is
Array(6) [ "{FOO: \"Hello", "World\"}", "\"BAR\": \"Nice", "To", "Meet", "You\"}" ]
that's expected, but I want to get an output like this:
Array(2) [ '{FOO: "Hello\nWorld"}', '{"BAR": "Nice\nTo\nMeet\nYou"}' ]
Edit: I will always have a } after a "invisible" newline
You can use Regular expression /\{[^}]+\}/g:
const str = `{FOO: "Hello\nWorld"}
{"BAR": "Nice\nTo\nMeet\nYou"}`
const result = str.match(/\{[^}]+\}/g)
console.log(result)
As far as the JavaScript string is concerned, there is no difference between these:
var a = `
`;
var b = `\n`;
console.log(a === b); // outputs true
In other words, there's no difference between the string in your post, and these two:
const str2 = `{FOO: "Hello\nWorld"}\n{"BAR": "Nice\nTo\nMeet\nYou"}`
const str3 = `{FOO: "Hello
World"}\n{"BAR": "Nice
To
Meet
You"}`
So there's no way to differentiate between these once they're in a JavaScript string. However, if you have an NDJSON file like this:
{"FOO": "Hello\nWorld"}
{"BAR": "Nice\nTo\nMeet\nYou"}
... and you load the contents of that file into JSON as a string, you end up with a string that's equivalent to this:
const str = `{FOO: "Hello\\nWorld"}
{"BAR": "Nice\\nTo\\nMeet\\nYou"}`
... which means your current code should work just fine.
If, on the other hand, your file looks like this:
{FOO: Hello
World"}
{"BAR": "Nice
To
Meet
You"}
... then you're dealing with a domain-specific language and the only reliable way to get what you want from it is to write your own parser that takes into account things like:
close-braces inside of strings ({"Hello}\nWorld"})
escaped double-quotes ({"Hello World\"}\n"})
any other oddity that the source might have (shouldn't FOO have quotes around it?)
I have some poorly formatted JSON which has newlines in the keys.
The raw JSON looks like:
{
"Some\\nKey": "Some\\nvalue",
"Some nice key": "Some nice value"
}
I have a matching data source which, for keys without newlines works very happily to convert "Some nice key" into "Some nice value". However, when the matching data source contains newlines, the lookup fails. My code looks something like:
const translations = JSON.parse(fileContents);
var value = translations[key];
if (value == null) {
const fixedKey = JSON.stringify(key).replace(/\\n/g, "\\\\n");
value = translations[fixedKey];
if (value == null) {
console.log(`Translation missing for key: ${fixedKey}`);
}
}
The console output is Translation missing for key: Some\\nKey
So, my question is: In Javascript, how do I look up a value of a JSON key with new lines in the key?
Edit:
So, there were two problems with my code - one external to the post, and one internal. The external problem is that the keys coming from the external data source were somehow malformed when doing lookups with newlines - no idea what, but they were. The second issue was with creating fixedKey - JSON.stringify adds " characters to the start and end, so my console output that I originally put was a lie because I wasn't copy/pasting but recreating by hand - I had missed that the output was actually Translation missing for key: "Some\\nKey". The resulting fix was const fixedKey = JSON.stringify(key).replace(/^"/, "").replace(/"$/,""); - using Stringify and stripping the leading and trailing " characters.
This works for me. Not sure what your issue is
const key = "Some Key";
const jsonString = `{ "Some\\nKey": "Some\\nvalue", "Some nice key": "Some nice value" }`
const obj = JSON.parse(jsonString.replaceAll(/\\n/g," "))
console.log(obj[key])
This also works but shows the value with a newline
const key = "Some\nKey";
const jsonString = `{ "Some\\nKey": "Some\\nvalue", "Some nice key": "Some nice value" }`
const obj = JSON.parse(jsonString)
console.log(obj[key])
If your keys and values are the same, it shouldn't matter that there's a \n in the key or not.
const translations = {
"test\n1": 1,
"test\\n2": 2
};
console.log(translations["test\n1"]);
console.log(translations["test\\n2"]);
I have a string that is a normal sentence. I need to replace the characters in the string if they are found in a given array. For example,
const arr = ["(model: Audi)", "(model: Kia)"];
if the string is:
"How is your (model: Audi) today?";
The result should be "How is your Audi today?".
Is there a way to do this with regex? I read somewhere that regex has better performance. I've tried looping thru the array then replacing the characters but I couldnt get it working and my solution would have nested loops due to the given string
const arr = ["(model: Audi)", "(model: Kia)"];
let string = "How is your (model: Audi) today?";
for (let data of arr){
string = string.replace(data, data.split(": ")[1].replace(")",""))
}
console.log(string);
Well, this is the simplest I have managed to do.
However, I myself kind of consider it a bad solution.
Let me know if it helps at all...
const myString = "How is your (model: Audi) today?";
const arr = ["(model: Audi)", "(model: Kia)"];
arr.forEach(item => {
const part = item.match(/\w+\)$/)[0];
const subPart = part.substring(0, part.length - 1);
if (myString.includes(item))
console.log(myString.replace(item, subPart));
});
First a quick definition :)
Template - A string which may contain placeholders (example:"hello [name]")
Placeholder - A substring whitin square brackets (example: "name" in "hello [name]:).
Properties map - A valid object with strings as values
I need to write a code that replace placeholders (along with brackets) with the matching values in the properties map.
example:
for the following properties map:
{
"name":"world",
"my":"beautiful",
"a":"[b]",
"b":"c",
"c":"my"
}
Expected results:
"hello name" -> "hello name"
"hello [name]" -> "hello world"
"[b]" -> "c"
"[a]" -> "c" (because [a]->[b]->[c])
"[[b]]" -> "my" (because [[b]]->[c]->my)
"hello [my] [name]" -> "hello beautiful world"
var map = {
"name":"world",
"my":"beautiful",
"a":"[b]",
"b":"c",
"c":"my"
};
var str = "hello [my] [name] [[b]]";
do {
var strBeforeReplace = str;
for (var k in map) {
if (!map.hasOwnProperty(k)) continue;
var needle = "[" + k + "]";
str = str.replace(needle, map[k]);
}
var strChanged = str !== strBeforeReplace;
} while (strChanged);
document.write(str); //hello beautiful world my
The answer by #chris is excellent, I just want to provide an alternative solution using regular expressions that works "the other way round", i.e., not by looking for occurrences of the "placeholder versions" of all items in the properties map, but by repeatedly looking for occurrences of the placeholder itself, and substituting it with the corresponding value from the property map. This has two advantages:
If the property map grows very large, this solution should have
better performance (still to be benchmarked though).
The placeholder and the way substitutions work can easily be modified by adjusting the regular expression and the substitution function (might not be an issue here).
The downside is, of course, that the code is a little more complex (partly due to the fact that JavaScript lacks a nice way of substituting regular expression matches using custom functions, so that's what substituteRegExp is for):
function substituteRegExp(string, regexp, f) {
// substitute all matches of regexp in string with the value
// returned by f given a match and the corresponding group values
var found;
var lastIndex = 0;
var result = "";
while (found = regexp.exec(string)) {
var subst = f.apply(this, found);
result += string.slice(lastIndex, found.index) + subst;
lastIndex = found.index + found[0].length;
}
result += string.slice(lastIndex);
return result;
}
function templateReplace(string, values) {
// repeatedly substitute [key] placeholders in string by values[key]
var placeholder = /\[([a-zA-Z0-9]+)\]/g;
while (true) {
var newString = substituteRegExp(string, placeholder, function(match, key) {
return values[key];
});
if (newString == string)
break;
string = newString;
}
return string;
}
alert(templateReplace("hello [[b]] [my] [name]", {
"name":"world",
"my":"beautiful",
"a":"[b]",
"b":"c",
"c":"my"
})); // -> "hello my beautiful world"
Update: I did some little profiling to compare the two solutions (jsFiddle at http://jsfiddle.net/n8Fyv/1/, I also used Firebug). While #chris' solution is faster for small strings (no need for parsing the regular expression etc), this solution performs a lot better for large strings (in the order of thousands of characters). I did not compare for different sizes of the property map, but expect even bigger differences there.
In theory, this solution has runtime O(k n) where k is the depth of nesting of placeholders and n is the length of the string (assuming dictionary/hash lookups need constant time), while #chris' solution is O(k n m) where m is the number of items in the property map. All of this is only relevant for large inputs, of course.
If you're familiar with .NET's String.Format, then you should take a look at this JavaScript implementation. It supports number formatting too, just like String.Format.
Here's an example of how to use it:
var result = String.Format("Hello {my} {name}", map);
However, it would require some modification to do recursive templates.
I am trying to use JavaScript to dynamically replace content inside of curly braces. Here is an example of my code:
var myString = "This is {name}'s {adjective} {type} in JavaScript! Yes, a {type}!";
var replaceArray = ['name', 'adjective', 'type'];
var replaceWith = ['John', 'simple', 'string'];
for(var i = 0; i <= replaceArray.length - 1; i ++) {
myString.replace(/\{replaceArray[i]\}/gi, replaceWith[i]);
}
alert(myString);
The above code, should, output "This is John's simple string in JavaScript! Yes, a string!".
Here is what happens:
we are given a string with values in braces that need replaced
a loop uses "replaceArray" to find all of the values in curly braces that will need replaced
these values, along with the curly braces, will be replaced with the corresponding values in the "replaceWith" array
However, I am not having any luck, especially since one value may be replaced in multiple locations, and that I am dealing a dynamic value inside of the regular expression.
Can anyone help me fix this, using a similar setup as above?
First, String.replace is not destructive - it doesn't change the string itself, so you'll have to set myString = myString.replace(...). Second, you can create RegExp objects dynamically with new RegExp, so the result of all that would be:
var myString = "This is {name}'s {adjective} {type} in JavaScript! Yes, a {type}!",
replaceArray = ['name', 'adjective', 'type'],
replaceWith = ['John', 'simple', 'string'];
for(var i = 0; i < replaceArray.length; i++) {
myString = myString.replace(new RegExp('{' + replaceArray[i] + '}', 'gi'), replaceWith[i]);
}
The best way I have found to do this, is to use an in-line replace function like others have mentioned, and from whom I borrowed. Special shout out to #yannic-hamann for the regex and clear example. I am not worried about performance, as I am only doing this to construct paths.
I found my solution in MDN's docs.
const interpolateUrl = (string, values) => string.replace(/{(.*?)}/g, (match, offset) => values[offset]);
const path = 'theresalways/{what}/inthe/{fruit}-stand/{who}';
const paths = {
what: 'money',
fruit: 'banana',
who: 'michael',
};
const expected = 'theresalways/money/inthe/banana-stand/michael';
const url = interpolateUrl(path, paths);
console.log(`Is Equal: ${expected === url}`);
console.log(`URL: ${url}`)
Strings are immutable
Strings in JavaScript are immutable. It means that this will never work as you expect:
myString.replace(x, y);
alert(myString);
This is not just a problem with .replace() - nothing can mutate a string in JavaScript. What you can do instead is:
myString = myString.replace(x, y);
alert(myString);
Regex literals don't interpolate values
Regular expression literals in JavaScript don't interpolate values so this will still not work:
myString = myString.replace(/\{replaceArray[i]\}/gi, replaceWith[i]);
You have to do something like this instead:
myString = myString.replace(new RegExp('\{'+replaceArray[i]+'\}', 'gi'), replaceWith[i]);
But this is a little bit messy, so you may create a list of regexes first:
var regexes = replaceArray.map(function (string) {
return new RegExp('\{' + string + '\}', 'gi');
});
for(var i = 0; i < replaceArray.length; i ++) {
myString = myString.replace(regexes[i], replaceWith[i]);
}
As you can see, you can also use i < replaceArray.length instead of i <= replaceArray.length - 1 to simplify your loop condition.
Update 2017
Now you can make it even simpler:
var regexes = replaceArray.map(string => new RegExp(`\{${string}\}`, 'gi'));
for(var i = 0; i < replaceArray.length; i ++) {
myString = myString.replace(regexes[i], replaceWith[i]);
}
Without a loop
Instead of looping and applying .replace() function over and over again, you can do it only once like this:
var mapping = {};
replaceArray.forEach((e,i) => mapping[`{${e}}`] = replaceWith[i]);
myString = myString.replace(/\{\w+\}/ig, n => mapping[n]);
See DEMO.
Templating engines
You are basically creating your own templating engine. If you want to use a ready solution instead, then consider using:
John Resig's Micro-Templating
Mustache
jQuery Templates
Handlebars
doT.js
or something like that.
An example of what you are trying to do using Mustache would be:
var myString = "This is {{name}}'s {{adjective}} {{type}} in JavaScript! Yes, a {{type}}!";
var myData = {name: 'John', adjective: 'simple', type: 'string'};
myString = Mustache.to_html(myString, myData);
alert(myString);
See DEMO.
Here's a function that takes the string and an array of replacements. It's flexible enough to be re-used. The only catch is, you need to use numbers in your string instead of strings. e.g.,
var str = "{0} membership will start on {1} and expire on {2}.";
var arr = ["Jamie's", '11/27/14', '11/27/15'];
function personalizeString(string, replacementArray) {
return string.replace(/{(\d+)}/g, function(match, g1) {
return replacementArray[g1];
});
}
console.log(
personalizeString(str, arr)
)
Demo: https://jsfiddle.net/4cfy7qvn/
I really like rsp's answer. Especially the 'Without a loop' section. Nonetheless, I find the code not that intuitive. I understand that this question comes from the two arrays scenario and that is more than 7 years old, but since this question appears as #1 on google when searching to replace a string with curly braces and the author asked for a similar setup I am tempted to provide another solution.
That being said, a copy and paste solution to play around with:
var myString = "This is {name}'s {adjective} {TYPE} in JavaScript! Yes, a { type }!";
var regex = /{(.*?)}/g;
myString.replace(regex, (m, c) => ({
"name": "John",
"adjective": "simple",
"type": "string"
})[c.trim().toLowerCase()]);
This resource really helped me to build and understand the code above and to learn more about regex with JavaScript in general.