2

I am trying to change a json value with typescript with a recursive function. I know how to show all keys or values, but I don't know exactly how to change the value.

I have this json and this code:


var json = {
    "a" : "hello",
    "b" : "bye",
    "c" : {
        "d" : "test1",
        "e" : "test2"
    },
    "f" : {
        "g" : {
             "h" : "test3",
             "i" : "test4"
         }
    }
};

changeInJson(id: string, value: string, level: number, json: object): void {
    level = level || 0;
    for (var property in json) {
        if (typeof json[property] === 'object') {
            this.changeInJson(id, value, ++level, json[property]);
        } else {
            console.log(property);
            if(property === id) {
                //change value in json
                console.log("Yes");
            }
        }
    }
};

changeInJson("i", "changed", 0, json);
console.log(json);

In this example, I know the json keys but in reality I need to save somehow all the path to arrive to the key "i" and this is what I don't know how to do.

Thanks

dgalan
  • 79
  • 7
  • What is `changeInJson("i", "changed", 0, json);` meant to change? There is no `i` at level 0... – spender Apr 16 '19 at 11:15
  • ...or do you want to change all `i`, irrespective of depth? – spender Apr 16 '19 at 11:17
  • In this case, I start at level 0 because I want iterate all the json and when the function is called again, increase the level. The final objective in this case is to change the value of 'i' without knowing that the value of 'i' is in f.g.i – dgalan Apr 16 '19 at 11:26
  • See my answer. Level is irrelevant here, and the terminating case of the recursion is that the object contains no more objects. – spender Apr 16 '19 at 11:27
  • @dgalan I think you can use Lodash here. https://lodash.com/docs/4.17.11#set – Kushagra Saxena Apr 16 '19 at 11:28
  • @spender the level is not irrelevant here. Please check my comment on your answer. – Kushagra Saxena Apr 16 '19 at 11:29
  • You have an _Object_, not _JSON_. JSON is always a String. – Mulan Apr 16 '19 at 14:24

2 Answers2

3

For this particular recursive task, it's not necessary to track the depth (or level) of the recursion, as the terminating recursive case is reached naturally when the object being considered has no sub-objects. A consideration here is that objects that contain circular references will blow up the recursion unless you take special measures to track what you've already seen.

A generalized approach to changing any prop/nested prop with the name id would be something akin to:

const changeInJson = (id: string, value: string, obj: object): void => {
  for (const [k, v] of Object.entries(obj)) {
    if (k === id) {
      obj[k] = value;
    } else if (typeof v === "object") {
      changeInJson(id, value, v);
    }
  }
};

IIRC you'll need at least es2017 lib in your tsconfig.json file for Object.entries

I'll leave the addition of array handling as an exercise for the reader :)

spender
  • 117,338
  • 33
  • 229
  • 351
  • I think I understand the usage of level here. Consider a usage: ``` { "a": "Test", b: { "a": "value" } } ``` And now the user wants to edit the inner `a` and not the outer `a`. @dgalan right? – Kushagra Saxena Apr 16 '19 at 11:27
  • @KushagraSaxena I think `level` is just an artefact of the OPs misunderstanding of recursion. Awaiting confirmation. – spender Apr 16 '19 at 11:29
  • I think it would be best to use. https://lodash.com/docs/4.17.11#set. What do you think? – Kushagra Saxena Apr 16 '19 at 11:30
  • OP has indicated that they *don't know* the path `f.g.i`, so I can't see how that would help. – spender Apr 16 '19 at 11:32
  • Yeah I understand that but I still believe OP has the same understanding as I have fi that's the case then we can use the lodash as I mentioned but if what you are saying is correct i.e OP does not have clear understanding of level then obviously just removing the level and proceeding with your solution would work. – Kushagra Saxena Apr 16 '19 at 11:34
  • But I am still in the opinion that above solution won't work with the situation that I pasted in my first comment on this answer. – Kushagra Saxena Apr 16 '19 at 11:35
  • @KushagraSaxena I can not use it, because I don't know the path. – dgalan Apr 16 '19 at 11:37
  • @dgalan In that case can you please explain the use case of level in you snippet. – Kushagra Saxena Apr 16 '19 at 11:41
  • 1
    @spender You are right, and I was wrong with the level. It is not necessary. Your code works perfectly! Thank you both for your help! – dgalan Apr 16 '19 at 11:41
  • 1
    @dgalan Thanks for confirming. I updated my answer, and added some interesting extra info about circ references. – spender Apr 16 '19 at 11:47
0

@spender 's solution is almost good, exception it bugs on null values...

Here is the fix:

const changeInJson = (id: string, value: string, obj: object): void => {
  for (const [k, v] of Object.entries(obj)) {
    if (k === id) {
      obj[k] = value;
    } else if (v && typeof v === "object") {
      changeInJson(id, value, v);
    }
  }
};
Pigeo
  • 55
  • 5