# Minecraft Language Files Updater

In 1.13, Minecraft language files were switched from being a simple multi-line key=value format to JSON.

# Challenge

Write a program converting from the original format returning a JSON string. Input can be taken using any standard input method, output must be json from any standard output method

The original format contains lines with key=value pairs, for example

tile.dirt.name=Dirt
advMode.nearestPlayer=Use "@p" to target nearest player

build.tooHigh=Height limit for building is %s blocks


Should be converted to one large JSON object with key=value

{
"tile.dirt.name": "Dirt",
"advMode.nearestPlayer": "Use \"@p\" to target nearest player",
"build.tooHigh": "Height limit for building is %s blocks"
}


## Some details

• Any valid JSON is allowed as long as it contains only the correct key/value pairs. Trailing commas are allowed because Minecraft allows them.
• The only things that must be escaped are quotes. (No newlines, backslashes, or other json-breaking things existed in the language file prior to 1.13)
• Empty lines should be ignored
• Lines contain exactly one equals

# Test Cases

Input:

tile.dirt.name=Dirt
advMode.nearestPlayer=Use "@p" to target nearest player

build.tooHigh=Height limit for building is %s blocks


Output:

{
"tile.dirt.name": "Dirt",
"advMode.nearestPlayer": "Use \"@p\" to target nearest player",
"build.tooHigh": "Height limit for building is %s blocks"
}


Input:

translation.test.none=Hello, world!
translation.test.complex=Prefix, %s%2$s again %s and %1$s lastly %s and also %1$s again! translation.test.escape=%%s %%%s %%%%s %%%%%s translation.test.invalid=hi % translation.test.invalid2=hi % s translation.test.args=%s %s translation.test.world=world  Output: { "translation.test.none": "Hello, world!", "translation.test.complex": "Prefix, %s%2$s again %s and %1$s lastly %s and also %1$s again!",
"translation.test.escape": "%%s %%%s %%%%s %%%%%s",
"translation.test.invalid": "hi %",
"translation.test.invalid2": "hi %  s",
"translation.test.args": "%s %s",
"translation.test.world": "world",
}


Input:

stat.mineBlock=%1$s Mined stat.craftItem=%1$s Crafted
stat.useItem=%1$s Used stat.breakItem=%1$s Depleted


Output:

{
"stat.mineBlock": "%1$s Mined", "stat.craftItem": "%1$s Crafted",
"stat.useItem": "%1$s Used", "stat.breakItem": "%1$s Depleted"
}

• How does tile.dirt.name become "block.minecraft.dirt"? – Pavel Aug 9 '18 at 2:34
• @Pavel uuh... whoops. Fixed that. That was unintentional – pfg Aug 9 '18 at 2:46
• Is it guaranteed that each non-empty line contains exactly 1 =? – user202729 Aug 9 '18 at 5:19
• @user202729 yes – pfg Aug 9 '18 at 17:58
• I'd be willing to bet that you actually need a solution to this problem and intend to use one to convert your files. :) – mbomb007 Aug 9 '18 at 19:24

# Python 3, 91 77 bytes

-14 Bytes thanks to OMᗺ

I thought that the printout of a Python dictionary would be close enough to JSON to make it a very competitive language for this challenge. However, the string representation of python dictionaries is different enough from JSON that I had better luck using python's built-in JSON library. I'll bet this can be done more succinctly in JavaScript.

import json
f=lambda x:json.dumps(dict(i.split("=")for i in x.split("\n")if i))


Try it Online!

Edit:

# Bash + Sed, 68 63 bytes

Bug fix thanks to OMᗺ and Night 2
-5 Bytes thanks to OMᗺ

I realized that it might be more byte efficient to directly convert the text to JSON without bundling it in an object, as was my approach for the python solution. Per byte, sed is the most powerful language for regex replacement that I know of.

echo {echo "$1"|sed 's/"/\\\"/g;s/$$.*$$=$$.*$$/"\1":"\2",/'}  Try it Online! ## Explanation echo { # prints the leading curly brace echo "$1"|sed                     # feeds the input into sed
's/"/\\"/g;                       # replaces " with \"
s/$$.*$$=$$.*$$/"\1":"\2",/'      # surrounds the left and right hand sides of the equals with quotes and joins them with a colon
}                                       # prints the closing curly brace

• If you're answering in two different languages, feel free to post that as two separate answers. – mbomb007 Aug 9 '18 at 19:23
• For the bash+sed answer, try using the -r flag for sed (+3 bytes) so that you do not need to escape the capturing groups (-4 bytes) tio.run/##LYq7CgIxEEX7/… – Kritixi Lithos Aug 28 '18 at 15:26

# Vim, 44 bytes

O{<Esc>:%s/"/\\"/g|%s/\v(.*)\=(.*)/"\1":"\2",
o}


Explanation:

O{<Esc>                                           Prepend {
:%s/"/\\"/g                                Escape all "
|%s/\v(.*)\=(.*)/"\1":"\2",     Json-ify lines
o}                                                Append }


# Rust, 150 bytes

|s:String|s.replace('"',"\\\"").split('\n').filter(|l|l.len()>0).map(|l|format!("\"")+&l.replace('=',"\":\"")+"\",").fold(format!("{{"),|r,n|r+&n)+"}"


Try it online!

Is it longer than Java?

# Retina 0.8.2, 35 bytes

"
\"
=
": "
G.
.+
"$&", ^ {¶$
¶}


Try it online! Would be 34 bytes in Retina 1 as you can use L$.+ instead of G. and .+. Explanation: " \"  Escape the quotes. = ": "  Fix up the key/value separator. (If the value might contain a =, use 1= at a cost of 2 bytes.) G.  Remove empty lines. .+ "$&",


Wrap each line in quotes. (The inner quotes were added earlier.)

^
{¶
$¶}  Wrap the entire output in {}s. # Husk, 22 bytes String manipulation is not really Husk's strength, but it did pretty well: J"{}"J',mȯJ':msx'=fI¶  Try it online!  ¶ -- split on newlines fI -- filter by identity (ie. remove empty strings) m( ) -- with each line x'= -- | split on '=' ms -- | show each (ie. enclose in quotes and escape quotes) J': -- | join with ':' J', -- join these with ',' J"{}" -- join the string "{}" with the result  • Ironically, there is something called a "Husk" in Minecraft! – Redwolf Programs Aug 9 '18 at 22:01 # Ruby, 56 bytes ->x{x.split(?\n).map{|i|i.split(?=)}.to_h.to_json}  +6 bytes for -rjson interpreter flag. Try it online! • @Piccolo did you pass the -rjson flag? – pfg Aug 10 '18 at 16:28 • @pfg Wow, I really dropped the ball on that haha. I had not only forgotten to use -rjson, but also assumed without actually checking that the error was the same one that I had gotten earlier involving to_h – Piccolo Aug 10 '18 at 18:03 # Perl 5-nl -M5.010, 58 54 bytes BEGIN{say'{'}s'"'\"'g;/=/&&say qq|"$": "$'",|}{say'}'  Try it online! 58 byte version: BEGIN{say'{'}s'"'\"'g;s/(.*)=(.*)/"$1": "$2",/;END{say'}'}  Try it online! • Both versions add a comma after every key:value pair, which is technically not compliant JSON (the final comma before the closing } should be omitted and will fail most strict JSON validators). Here's a quick 58-byte rewrite which produces valid (if uglier for human readers) JSON: $c||='{';s'"'\"'g;/=/&&say qq|$c"$":"$'"|;$c=','}{say'}' I expect you can find something a bit shorter/more elegant. – mousetrapper Aug 15 '18 at 19:37
• @mousetrapper That's a nice way to avoid the BEGIN. OP explicitly allows trailing commas though: "Trailing commas are allowed because Minecraft allows them.". Feel free to post that as a new answer, mentioning the difference. – sundar - Reinstate Monica Aug 15 '18 at 20:03
• Ah, yes, good point, missed that sentence in the original post. The default assignment only makes sense if you're trying to vary the first character, otherwise your BEGIN is still shorter in the case where you just want to emit the '{'. I like your END-avoiding technique. I knew that -n placed an effective while(<>){} loop around your code; I had no idea just how literal that was. – mousetrapper Aug 15 '18 at 21:27
• I was pretty surprised too, when I first found that out. It's one of those Perl features that straddles the line between a weird hack and a brilliant way to do TIMTOWDI. I had forgotten about it though, so credit for it in this instance goes to Dennis in the Perl 5 golfing tips thread. – sundar - Reinstate Monica Aug 15 '18 at 21:43

-4 bytes thanks to Laikoni (using do-notation over list-comprehension)!

Works with multiple = on one line:

f s='{':do{(a,_:b)<-span(/='=')<$>lines s;show a++':':show b++","}++"}"  Try it online! ## Explanation The term span(/='=')<$>lines s splits the string on the first =, leaving us with ("<initial part>","=<remaining line>"). Doing a pattern-match (a,_:b) ensures that the line was not empty and at the same time removes the leading =.

Now we only need to show both a and b (enclosing it in quotes and escaping quotes), do some formatting (: and , characters) and finally enclose it in {}.

• 71 bytes using do: Try it online! – Laikoni Aug 11 '18 at 11:45

# C (gcc), 243 219 bytes

Thanks to ceilingcat for the suggestion.

I decided to use a state machine to handle the three cases (newline, key, value) and it turned out pretty well. Also, I got to abuse the fall-through feature of switch and the macro stringizing operator!

Although the challenge didn't require it, I also escaped the \ character per the JSON spec. If that character will never be in the input, then &&c-92 can be removed for 5 more bytes.

#define p(s)printf(#s,c)
#define a(i)case i:
c,s;f(){for(p({);(c=getchar())>0;)switch(s){a(0)if(c<11)break;s++,p(\42);a(1)c==61?s++,p(":"):p(%c);break;a(2)c-34&&c-92?c==10?p(\42\54),s=0:p(%c):p(\\%c);}s-2||p(\42);p(});}


Try it online!

## Original submission: 243 bytes

The original submission kept unneeded spacing as in the provided JSON examples.

#define p(s)printf(s,c)
#define a(i)case i:
c,s;f(){for(p("{\n");(c=getchar())>0;)switch(s){a(0)if(c<11)break;s++,p("  \"");a(1)c==61?s++,p("\": \""):p("%c");break;a(2)c-34&&c-39?c==10?p("\",\n"),s=0:p("%c"):p("\\%c");}s==2&&p("\"\n");p("}");}


Try it online!

# JavaScript, 6663 62 bytes

s=>JSON.stringify(o=/(.+)=(.+)/g,s.replace(o,(_,a,b)=>o[a]=b))


f=
s=>JSON.stringify(o=/(.+)=(.+)/g,s.replace(o,(_,a,b)=>o[a]=b))

console.log(
f(tile.dirt.name=Dirt
advMode.nearestPlayer=Use "@p" to target nearest player

build.tooHigh=Height limit for building is %s blocks)
)

-3 bytes thanks to @redundancy

-1 byte thanks to @l4m2

# Retina 0.8.2, 43 bytes

¶¶
¶
"
\"
=
": "
m$|^ " m^$
¶}
^
{¶


Try it online!

# Perl 6, 48 bytes

{to-json %(.lines.grep(?*)>>.split("=",2).flat)}


2 bytes less if we can assume exactly 1 equals sign on a non-empty line.

Try it online!

Ungolfed:

{                   # An anonymous block, taking 1 string which ends in $_. to-json # Convert a Perl 6 number, string, list or hash to JSON and return it. %( # Force to hash (dictionary) .lines # Break$_ (implicitly assumed) into a list of lines.
.grep(?*)   # Pick only those that are True (non-empty).
>>.         # For each element in the list, call the following method ...
split("=",2) # ... split the string at =, making at most 2 chunks.
.flat       # That gives a list of 2-element lists. Flatten it.
)               # List is converted into the hash like this: { first element => second element, third => fourth, ... }
}                   # Implicitly return


By the way, the to-json routine is deprecated, as the compiler will tell you, but who cares.

# Python 2, 81 bytes

lambda s:'{'+re.sub(r'(.*)=(.*)',r'"\1":"\2",',re.sub('"',r'\"',s))+'}'
import re


Try it online!

## Ruby, 59 + 5 = 64

Needs -rjson (+5)

->c{Hash[*c.split(?\n).map{|l|l.split ?=}.flatten].to_json}


Explanation:

->c{                                                      } # anonymous function with param c
Hash[*                                       ]          # converts ["a", "b", "c", "d"] into {"a": "b", "c": "d"}
c.split(?\n)                                      # splits c into lines
.map{|l|          }                   # map lines so each element represents
l.split ?=                    # an array of itself but split by =
.flatten           # merges 2d array to 1d (also gets rid of empty elements for newlines
.to_json  # converts hash to json


# JavaScript (ES6), 66 bytes

s=>{${s.replace(/"/g,'\\"').replace(/(.*)=(.*)/g,'"$1":"$2",')}}  Assumes there's only one = per line ## Testing snippet f=s=>{${s.replace(/"/g,'\\"').replace(/(.*)=(.*)/g,'"$1":"$2",')}}
<textarea id="i" onkeyup="o.innerText=f(i.value)"></textarea><pre id="o">

• Should be 66 bytes. \\ might've been parsed as \ when counting length. – redundancy Aug 10 '18 at 7:49
• @redundancy I really should stop using "code".length in the javascript console to count the length – Herman L Aug 12 '18 at 18:19

# V, 30 bytes

O{␛Í"/\\"
ggòeÉ"vyf=Plp$pa,òo}  Expects one input at a time. The TIO snippet runs all given test cases as a single input. I'm new to V's extended mappings, so tips are always welcome! Try it online! ## Explanation O{␛ # insert { on a new line above Í # global substitution across all lines "/\\" # " => \" gg # go to first line ò # recursively... e # forward to end of word; if at end of line, applies to next word below É" # prepend " to first non-whitespace char vy # copy current character (i.e. ") f=Plp # paste " before and after the next =$pa,    #   paste " at end of line and append ,
ò   # ...end
o} # insert } on a new line below


# C (gcc), 172 bytes

#define p(s)printf(#s,c)
c,s;f(){for(p({);~(c=getchar());)s-2?c>10|s&&(s||(s+=p(\42)),c==61?s++,p(":"):p(%c)):c-34&&c-92?c==10?s=!p(\42\54):p(%c):p(\\%c);s-2||p(\42);p(});}


Try it online!

Based on @ErikF's implementation but without switch/case.

Slightly ungolfed version

#define p(s)printf(#s,c)
c,s;
f(){
for(p({);~(c=getchar());)
s-2?
c>10|s&&(
s||
(s+=p(\42)),
c==61?
s++,
p(":")
:
p(%c)
)
:
c-34&&c-92?
c==10?
s=!p(\42\54)
:
p(%c)
:
p(\\%c);
s-2||p(\42);
p(});
}


# R, 118 bytes

function(s){cat(paste("{",gsub("(.*)=(.*)","\"\\1\":\"\\2\",",gsub("\"","\\\\\"",gsub("\n{2,}","\n",s)),perl=T),"}"))}


Try it online!

# C (gcc), 119 bytes

#define p(s)printf(#s,c)
s;f(c){for(p({);~(c=getchar())|s;)c<11?s=s&&!p(",�"):c-61?s++||p(\42),p(\\u%04x):p(":");p(});}


Try it online!

# PHP, 87 bytes

preg_match_all("/^(.*)=(.*)$/m",$argn,$m);echo json_encode(array_combine($m[1],$m[2]));  Run as pipe with -nR or try it online. Insert \s before $/m for Windows linebreaks; \s* if linebreaks are uncertain.
Insert U after $/m if values contain =. # Dart, 142114 108 bytes f(s)=>"""{${s.replaceAll('"','\\"').replaceAllMapped(RegExp(r'(.*)=(.*)'),(m)=>'"${m[1]}":"${m[2]}",')}}""";
`

Try it online!

• -28 bytes by getting rid of the json.encode function and using regular string building
• -6 bytes by removing the 'new' keyword and a few spaces