11
\$\begingroup\$

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

21 Answers 21

4
\$\begingroup\$

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
| improve this answer | |
\$\endgroup\$
  • 8
    \$\begingroup\$ If you're answering in two different languages, feel free to post that as two separate answers. \$\endgroup\$ – mbomb007 Aug 9 '18 at 19:23
  • \$\begingroup\$ 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/… \$\endgroup\$ – user41805 Aug 28 '18 at 15:26
4
\$\begingroup\$

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 }
| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

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?

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

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.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

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
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Ironically, there is something called a "Husk" in Minecraft! \$\endgroup\$ – Redwolf Programs Aug 9 '18 at 22:01
2
\$\begingroup\$

Ruby, 56 bytes

->x{x.split(?\n).map{|i|i.split(?=)}.to_h.to_json}

+6 bytes for -rjson interpreter flag.

Try it online!

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ @Piccolo did you pass the -rjson flag? \$\endgroup\$ – pfg Aug 10 '18 at 16:28
  • \$\begingroup\$ @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 \$\endgroup\$ – Piccolo Aug 10 '18 at 18:03
2
\$\begingroup\$

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!

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – mousetrapper Aug 15 '18 at 19:37
  • \$\begingroup\$ @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. \$\endgroup\$ – sundar - Reinstate Monica Aug 15 '18 at 20:03
  • \$\begingroup\$ 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. \$\endgroup\$ – mousetrapper Aug 15 '18 at 21:27
  • \$\begingroup\$ 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. \$\endgroup\$ – sundar - Reinstate Monica Aug 15 '18 at 21:43
2
\$\begingroup\$

Haskell, 75 71 bytes

-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 {}.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

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!

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

JavaScript, 66 63 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

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Retina 0.8.2, 43 bytes

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

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

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.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Python 2, 81 bytes

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

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

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
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

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">

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

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
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

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(});
}
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

R, 118 bytes

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

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

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!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

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 =.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Dart, 142 114 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
  • | improve this answer | |
    \$\endgroup\$

    Your Answer

    By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

    Not the answer you're looking for? Browse other questions tagged or ask your own question.