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\$
11
  • 1
    \$\begingroup\$ How does tile.dirt.name become "block.minecraft.dirt"? \$\endgroup\$
    – Pavel
    Commented Aug 9, 2018 at 2:34
  • \$\begingroup\$ @Pavel uuh... whoops. Fixed that. That was unintentional \$\endgroup\$
    – pfg
    Commented Aug 9, 2018 at 2:46
  • 6
    \$\begingroup\$ Is it guaranteed that each non-empty line contains exactly 1 =? \$\endgroup\$
    – user202729
    Commented Aug 9, 2018 at 5:19
  • \$\begingroup\$ @user202729 yes \$\endgroup\$
    – pfg
    Commented Aug 9, 2018 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
    Commented Aug 9, 2018 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
\$\endgroup\$
2
  • 8
    \$\begingroup\$ If you're answering in two different languages, feel free to post that as two separate answers. \$\endgroup\$
    – mbomb007
    Commented Aug 9, 2018 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
    Commented Aug 28, 2018 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 }
\$\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?

\$\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.

\$\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
\$\endgroup\$
1
  • \$\begingroup\$ Ironically, there is something called a "Husk" in Minecraft! \$\endgroup\$
    – rydwolf
    Commented Aug 9, 2018 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!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ @Piccolo did you pass the -rjson flag? \$\endgroup\$
    – pfg
    Commented Aug 10, 2018 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
    Commented Aug 10, 2018 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!

\$\endgroup\$
4
  • \$\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\$ Commented Aug 15, 2018 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 R
    Commented Aug 15, 2018 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\$ Commented Aug 15, 2018 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 R
    Commented Aug 15, 2018 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 {}.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ 71 bytes using do: Try it online! \$\endgroup\$
    – Laikoni
    Commented Aug 11, 2018 at 11:45
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!

\$\endgroup\$
0
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

\$\endgroup\$
3
1
\$\begingroup\$

Retina 0.8.2, 43 bytes

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

Try it online!

\$\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.

\$\endgroup\$
1
\$\begingroup\$

Python 2, 81 bytes

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

Try it online!

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

\$\endgroup\$
2
  • \$\begingroup\$ Should be 66 bytes. \\ might've been parsed as \ when counting length. \$\endgroup\$
    – redundancy
    Commented Aug 10, 2018 at 7:49
  • 1
    \$\begingroup\$ @redundancy I really should stop using "code".length in the javascript console to count the length \$\endgroup\$
    – Endenite
    Commented Aug 12, 2018 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
\$\endgroup\$
1
\$\begingroup\$

R, 118 bytes

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

Try it online!

\$\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!

\$\endgroup\$
0
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 =.

\$\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
  • \$\endgroup\$
    1
    \$\begingroup\$

    C (gcc), 172 170 bytes

    #define p(s)printf(#s,c)
    c,s;f(){for(p({);read(0,&c,1);)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({);read(0,&c,1);)
      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(});
    }
    
    \$\endgroup\$

    Your Answer

    By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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