Giesburts
Giesburts

Reputation: 7648

Convert yaml strings to JSON objects

We have two separated codebases that are using different styles of localization. One of the codebases is using yaml, the other is using JSON.

Right now, we're slowly migrating to the codebase with JSON but with 20k yaml strings and 7 different languages it's a pain in the ass to convert this all manually. Unfortunately we're using string notation and not object notation in our yaml files so a converter like this wouldn't work.

Example yaml

cart.title.primary: Cart
cart.title.secondary: Buy products
cart.dialog.title: Remove product
cart.dialog.text: Are you sure to remove this product?

Becomes in a converter this

{
  "cart.title.primary": "Cart",
  "cart.title.secondary": "Buy products",
  "cart.dialog.title": "Remove product",
  "cart.dialog.text": "Are you sure to remove this product?"
}

But what I want, is for each dot in the string actually an object in JSON. So ideally, the yaml I provided should become something like:

{
  "cart": {
    "title": {
      "primary": "Cart",
      "secondary: "Buy Products"
    },
    "dialog": {
      "title": "Remove product",
      "text": "Are you sure to remove this product?"
    }
  }
}

Is there someone with experience doing something like this? Pref. using PHP or JavaScript. Thanks in advance!

Upvotes: 1

Views: 5363

Answers (3)

Nenad Vracar
Nenad Vracar

Reputation: 122057

You can build this nested structure using split method to create path array from the keys and then reduce method to nest properties based on that keys array.

const yaml = {
  "cart.title.primary": "Cart",
  "cart.title.secondary": "Buy products",
  "cart.dialog.title": "Remove product",
  "cart.dialog.text": "Are you sure to remove this product?"
}

const toJson = (data) => {
  return Object.keys(data).reduce((a, k) => {
    k.split('.').reduce((r, e, i, a) => {
      return r[e] || (r[e] = (a[i + 1] ? {} : data[k]))
    }, a)
    return a
  }, {})
}

console.log(toJson(yaml))

You can also use split method to split the yaml string on new lines and then build the nested object with reduce.

const yaml = `
cart.title.primary: Cart
cart.title.secondary: Buy products
cart.dialog.title: Remove product
cart.dialog.text: Are you sure to remove this product?
`
const obj = yaml.split('\n').filter(Boolean).reduce((a, k) => {
  const [key, value] = k.split(': ')

  key.split('.').reduce((r, e, i, arr) => {
    return r[e] || (r[e] = (arr[i + 1] ? {} : value))
  }, a)

  return a;
}, {})

console.log(obj)

Upvotes: 0

eskwayrd
eskwayrd

Reputation: 4521

As a Node.js script:

#!/usr/bin/env node

const fs = require('fs')

var file = process.argv[process.argv.length - 1]
var json = {}
fs.readFileSync(file, { encoding: 'utf8' })
  .split(/\r?\n/)
  .forEach((line) => {
    [keyPath, value] = line.split(/: */)
    var target = json
    var keys = keyPath.split(/\./)
    var counter = 0
    keys.forEach((key) => {
      counter++
      if (counter === keys.length) target[key] = value
      else {
        if (!(key in target)) target[key] = {}
        target = target[key]
      }
    })
  })
console.log(JSON.stringify(json, null, 2))

To use it:

convert.js file.yaml

Output, using your example.yaml as input:

{
  "cart": {
    "title": {
      "primary": "Cart",
      "secondary": "Buy products"
    },
    "dialog": {
      "title": "Remove product",
      "text": "Are you sure to remove this product?"
    }
  }
}

Upvotes: 1

Nigel Ren
Nigel Ren

Reputation: 57121

You can use a combination of the basic loading of yaml, this just assumes a string and uses yaml_parse(), then using the code from Convert dot syntax like "this.that.other" to multi-dimensional array in PHP you can process each line at a time to create the new structure...

$yaml = 'cart.title.primary: Cart
cart.title.secondary: Buy products
cart.dialog.title: Remove product
cart.dialog.text: Are you sure to remove this product?';

$data = yaml_parse($yaml);

$output = [];
foreach ( $data as $key => $entry ) {
    assignArrayByPath($output, $key, $entry);
}

function assignArrayByPath(&$arr, $path, $value, $separator='.') {
    $keys = explode($separator, $path);

    foreach ($keys as $key) {
        $arr = &$arr[$key];
    }

    $arr = $value;
}

echo json_encode($output, JSON_PRETTY_PRINT);

which gives you

{
    "cart": {
        "title": {
            "primary": "Cart",
            "secondary": "Buy products"
        },
        "dialog": {
            "title": "Remove product",
            "text": "Are you sure to remove this product?"
        }
    }
}

Upvotes: 1

Related Questions