3

I am trying to make a universal function in javascript, that converts a json data structure to an OSC compatible format. OSC meaning '/' seperated address strings assigned to arguments of any type.

A nested json like this:

{
  "hello":"world",
  "one":{
    "two":{
      "three":[4, 5, 6, 7]
    },
    "deux":"trois",
    "zwei":3
  }
}

would result in:

[
  {
    "addr":"/hello", 
    "args":"world"
  },
  {
    "addr":"/one/two/three", 
    "args":[4, 5, 6, 7]
  },
  {
    "addr":"/one/deux", 
    "args":"trois"
  },
  {
    "addr":"/one/zwei", 
    "args":3
  },
]

I'm not a fan of recursive functions, but I thought that's the only way to go, so I came up with this:

example = {
  "hello":"world",
  "one":{
    "two":{
      "three":[4, 5, 6, 7]
    },
    "deux":"trois",
    "zwei":3
  }
}

toOSC(example)

function toOSC(json) {
  var osc_msg = [{address:""}]
  createPath(json, osc_msg,0,"")
  for(let o of osc_msg) {
    if(o.hasOwnProperty('args')) {
      console.log(o)
    }
  }
}

function createPath(obj, osc_msg, i, addr) {
  for(let m in obj) {
    osc_msg[i]['address'] += '/' + m

    if(Array.isArray(obj[m]) || typeof obj[m] !== 'object') {
      osc_msg[i]['args'] = obj[m]
      i++
      osc_msg.push({address:""})
    } else {
      i = createPath(obj[m], osc_msg, i, osc_msg[i].address)
      i++
      osc_msg.push({address:addr})
    }
  }
  return i
}

The code fails in the way that the second of two nested objects of the same depth, gets rid of the first part of its address and I can't get my head around it.

I'm happy for any idea, also concerning the general approach to convert json to an OSC compatible format.

I want to use the conversion for sending messages with node.js package osc-min.

lasse
  • 95
  • 8

2 Answers2

3

It is way easier if you pass down the previously traversed keys and yield up the results:

     function* format(obj, previous = "") {
       for(const [key, value] of Object.entries(obj)) {
         if(typeof value !== "object" || Array.isArray(value)) {
           yield { addr: previous + "/" + key, args: value };
         } else {
           yield* format(value, previous + "/" + key);
        }
      }
    }

    // That can be used as:

     const result = [...format({ a: { b: "test", d: { e: 1 }}, c: [1, 2, 3] })];
     
     console.log(result);
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • This was brilliant. I was racking my brain over this problem, couldn't figure it out. I'm not a good programmer :( – TKoL Feb 11 '19 at 16:56
  • 1
    wow, this does solve it! And saves so much space ;) I have to admit I do not fully understand whats happening there but I see I have to go understand yield and generator functions. For anyone else interested: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* Thanks a lot for this crazy quick and helpful response @Jonas – lasse Feb 11 '19 at 16:59
  • @lasse glad to help :) (any if you understand generators, it will be way easier to understand other very complicated things, such as continuations inn other languages) – Jonas Wilms Feb 11 '19 at 17:54
  • @TKoL yup, generators are the most unused great feature in JS :) (and everyone is using `.reduce` for no reason ...) – Jonas Wilms Feb 11 '19 at 17:56
1

This answer is ages and ages later, but I wanted to provide an answer that I actually understood, because I absolutely did not understand the generator answer.

function processObj(obj, path="/") {
  let arr = [];
  for (let key of Object.keys(obj)) {
    const value = obj[key];
     if(typeof value !== "object" || Array.isArray(value)) {
          arr.push({
            "addr": path + key,
            "args": value
          })
     } else {
       const toAdd = processObj(value, path + key + "/");
       arr = arr.concat(toAdd);
     }
  }
  return arr;
}
TKoL
  • 13,158
  • 3
  • 39
  • 73