5

Currently I'm looking to try to get something in a Hash format in Ruby from JS.

I have a JS module that looks something like this

module.exports = {
  key1: "val",
  key2: {
    key3: "val3"
  }
  ...
}

This is given to me as just one string. I'm aware that ExecJS could be helpful here but I'm not sure what the best way to convert it to a Hash would be.

I'm currently trying to do something like this

contents.prepend("var module = {exports: {}};\n")
context = ExecJS.compile contents

context.eval('module.exports').to_hash

But when giving it a string like above I'm getting a parsing error from the JSON gem.

Somnium
  • 363
  • 1
  • 8
  • 17
  • Are there a lot of these files you want to do this with? You could just copy paste the contents and replace the `module.exports = ` with some Ruby variable name. Can do find-and-replace if there are a lot of files. I suppose you could evaluate them with Node and then dump the output to files as well. – max pleaner May 15 '21 at 06:29
  • @maxpleaner it's part of a larger file ingestion system so amount is unknown – Somnium May 15 '21 at 18:18

2 Answers2

1

I just tried putting this in a script and running it:

require 'execjs'

str = <<-JS
  // Note, you could just do `var module = {}` here
  var module = {exports: {}};

  module.exports = {
    key1: "val",
    key2: {
      key3: "val3"
    }
  }
JS

context = ExecJS.compile str 
puts context.eval('module.exports').to_hash
# => {"key1"=>"val", "key2"=>{"key3"=>"val3"}}

Maybe you're getting the JSON parse error because the module contains something which can't be serialized.


Here's another approach.

Make a JS file which loads the module and exports it as JSON.

var fs = require('fs');
var hash = require('path/to/my_module.js');

fs.writeFileSync('path/to/output.json', JSON.stringify(hash), 'utf8');

Run that with nodejs my_script.js then read it from Ruby:

require 'json'
hash  = JSON.parse "path/to/output.json"
max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • Hm I can see my source has some HTML in a string in one of the keys. I wonder if that's it. – Somnium May 16 '21 at 02:02
  • I did some more testing and it doesn't look like that's the case. In my rails console I'm reading from a file. It roughly has about 2000 lines with varying depth of keys. I'm commenting certain lines and when I get to a certain number of lines commented it seems to just work... if I remove those comments the JSON parse error surfaces again. I know it must be difficult to envision the problem without me providing a source file... – Somnium May 16 '21 at 03:19
  • yeah, just use the commenting-out approach to narrow down the issue. need to know more specifics to help further. – max pleaner May 16 '21 at 06:10
  • I feel pretty convinced that the length of the JSON is erroring out some how. As I've commented out different lines, it seems as though at a certain limit, it doesn't matter which line I'm commenting out, it'll just fail to parse. It looks like the complete JSON being fed in as ExecJS is trying to evaluate it is literally too long (for whatever reason) and the logs show an incompleted JSON in the stack trace. – Somnium May 16 '21 at 06:21
  • See my edited answer for another approach that doesn't require ExecJS. Apparently there are limits on JSON file sizes, but that limit is super large (like 100 MB). – max pleaner May 16 '21 at 15:17
  • The "file" coming in is really string content I'm getting from a repository so the script needs to be relatively dynamic. The file size is only about 147K. Maybe the solution is that I use ExecJS but I do something similar to what's been proposed? It feels like this might be some weird bug on ExecJS's side. Or rather the JSON parse gem. – Somnium May 17 '21 at 14:27
  • So just to follow up I seem to have found an answer that'll work for me. I switched the Gem from `ExecJs` to `mini-racer` as the JS evaluator and everything worked out of the box. It seems to have been a real bug with ExecJS and the way the json gem was used. – Somnium May 17 '21 at 16:16
0

If the problem is the runtime, you can use mini_racer as the runtime used by ExecJS. Just include the gem:

gem 'execjs'
gem 'mini_racer'

Then you can run:

p ExecJS.runtime
# > #<ExecJS::MiniRacerRuntime:0x00007fc44ea20bd8>

Then you would setup the context first:

require 'execjs'

jscode = <<-JS
  module.exports = {
    key1: "val",
    key2: {
      key3: "val3"
    }
  }
JS

context = ExecJS.compile "var module = {};"
result_hash = context.eval jscode
puts result_hash["key1"]

Yason
  • 340
  • 2
  • 11