skip to Main Content

My first thought was a simple "search and replace of the underlying JSON code, inspecting it line by line. That will not work as the same "keyword" could be used in various places, but only one specific line needs to be changed.

Then I thought, read the existing data in, create a new entry, push the data into that block, and then delete the original key. The former works … but leaves the original and all its data intact.

Here is a simplified version (don’t think 500+ lines would be appreciated):

{
project 1:{
     ad_campaign:{
       ad_1{
          data1,
          data2
       },
       ad_2{
         data1,
         data2
       }
     }
}
project 2:{
     ad_campaign:{
     ad_1{
        data1,
        data2
        }
     ad_201{
       data1,
       data2
       }
     }
}
}

The "data" entries are 6 lines per ad, and there could be dozens of ads per project, and dozens of projects !!

They want to change Projec1 "ad_1" to "ad_101"

As you can see, both projects have ads named "ad_1", so a simple search and replace of the underlying text code file is a no-go.

If I use $project1->{"ad_campaign"}, I can get all the data. If I then use: $project1->{"ad_campaign"}=$new_ad_id … it creates a new section (which I =could= then copy the data line – by – line) … but I still cannot get rid of the original "ad_1" entry! ($project1->{"ad_campaign"}->{"ad_1"} =null (no bareword allowed) / "null" / "" have no effect – even when trying to remove the internal data first. That could result in data1:”,data2:” etc)

What I really need is a way to do $customer1->{"ad_campaign"}->{"new_ad_id"} to simply change that third key. (I’ve Googled "change" / "replace" JSON key, but no results … especially as I am using PERL)

One more "spanner in the works"; the file gets saved as a single line, (Using JSON::PP The "pretty" seems to add too many spaces – tab indents would seem better) so would be difficult to break apart, and do a line by line scan anyway. (Another idea was to reiterate through file, set a flag once hit "customer1", another loop, and another flag for "ad_campaign", and then final loop seeking "ad_1". Seems a bit inefficient … plus all the decoded data is in a hash anyway!)

Please bear in mind, I have simplified this code. Between "Project1" and "ad_campaign" could be another 50 key:value pairs PER PROJECT

There is possibly some simple solution finding / changing the value via a hash "argument" … but I’ve yet to find it!

  • Somewhere else I read: delete $JSON->{"project1"}->{"ad_campaign"}->{"ad_id"} but that didn’t delete the original either!

Just had a closing thought: Maybe I could do something like "indexOf" to locate project1/ad_campaign etc, and then do split/splice, pushing one half of data, then split again etc until I find the word to replace. But again, that does seem overkill for what could be a pretty basic problem

3

Answers


  1. Try code below.

    
    use strict ;
    use warnings;
    use feature 'say';
    
    use JSON;
    
    my $data = <<EOM;
    {
        "project 1": {
          "ad_campaign": {
            "ad_1": [
              "p1: ad:1 data1",
              "p1: ad:1 data2"
            ],
            "ad_2": [
              "p1: ad:2 data1",
              "p2: ad:2 data2"
            ]
          }
        },
        "project 2": {
          "ad_campaign": {
            "ad_1": [
              "p2: ad:1 data1",
              "p2: ad:1 data2"
            ],
            "ad_201": [
              "p2: ad:201 data1",
              "p2: ad:201 data2"
            ]
          }
        }
      }
    EOM
    
    my $in = from_json($data);
    
    # create "ad_101" to reference to the existing "ad_1" data tree
    $in->{"project 1"}{"ad_campaign"}{"ad_101"} = $in->{"project 1"}{"ad_campaign"}{"ad_1"};
    
    # now delete the reference to "ad_1"
    delete $in->{"project 1"}{"ad_campaign"}{"ad_1"};
    
    
    # using pretty & canonical here to make the results easier to read
    # don't use pretty if you want all JSON output as a single line
    say to_json($in, {pretty => 1, canonical => 1});
    

    output is

    {
       "project 1" : {
          "ad_campaign" : {
             "ad_101" : [
                "p1: ad:1 data1",
                "p1: ad:1 data2"
             ],
             "ad_2" : [
                "p1: ad:2 data1",
                "p2: ad:2 data2"
             ]
          }
       },
       "project 2" : {
          "ad_campaign" : {
             "ad_1" : [
                "p2: ad:1 data1",
                "p2: ad:1 data2"
             ],
             "ad_201" : [
                "p2: ad:201 data1",
                "p2: ad:201 data2"
             ]
          }
       }
    }
    

    Key point to note is that nested hashes in perl use references, which are akin to pointers in C/C++ land. Means that after running this line

    $in->{"project 1"}{"ad_campaign"}{"ad_101"} = $in->{"project 1"}{"ad_campaign"}{"ad_1"};
    

    both the ad_1 and ad_101 keys reference the same data, namely

    [
       "p1: ad:1 data1",
       "p1: ad:1 data2"
    ],
    

    [EDIT – answering some of the comments]

    I changed the data values to highlight that the reference in ad_1 and ad_101 are pointing to unique data in the JSON document. You didn’t supply any data, so I made some up.

    Use of from_json and to_json rather than decode_json & encode_json is purely to allow the pretty parameter to be controlled. Use the variant that suits your use-case. Check out the docs here

    Login or Signup to reply.
  2. Another option is to use jq, rather than Perl

    Assuming data.json contains this

    {
        "project 1": {
          "ad_campaign": {
            "ad_1": [
              "p1: ad:1 data1",
              "p1: ad:1 data2"
            ],
            "ad_2": [
              "p1: ad:2 data1",
              "p2: ad:2 data2"
            ]
          }
        },
        "project 2": {
          "ad_campaign": {
            "ad_1": [
              "p2: ad:1 data1",
              "p2: ad:1 data2"
            ],
            "ad_201": [
              "p2: ad:201 data1",
              "p2: ad:201 data2"
            ]
          }
        }
    }
    

    this one-liner to do the rename

    $ jq '."project 1".ad_campaign |= ( .ad_101 = .ad_1 | del(.ad_1) ) '  data.json 
    

    output is

    {
      "project 1": {
        "ad_campaign": {
          "ad_2": [
            "p1: ad:2 data1",
            "p2: ad:2 data2"
          ],
          "ad_101": [
            "p1: ad:1 data1",
            "p1: ad:1 data2"
          ]
        }
      },
      "project 2": {
        "ad_campaign": {
          "ad_1": [
            "p2: ad:1 data1",
            "p2: ad:1 data2"
          ],
          "ad_201": [
            "p2: ad:201 data1",
            "p2: ad:201 data2"
          ]
        }
      }
    }
    

    to get jq to output as a single line, add the -c option to the commandline

    $ jq -c  '."project 1".ad_campaign |= ( .ad_101 = .ad_1 | del(.ad_1) ) '  data.json 
    {"project 1":{"ad_campaign":{"ad_2":["p1: ad:2 data1","p2: ad:2 data2"],"ad_101":["p1: ad:1 data1","p1: ad:1 data2"]}},"project 2":{"ad_campaign":{"ad_1":["p2: ad:1 data1","p2: ad:1 data2"],"ad_201":["p2: ad:201 data1","p2: ad:201 data2"]}}}
    
    Login or Signup to reply.
  3. The others have answered this question adequately, and I’d also choose jq (see Using jq how can I replace the name of a key with something else and the jq FAQ).

    But, I want to emphasis what @pmqs is doing but not yelling from the rooftops.

    It’s just Perl

    If you want to do something in Perl, you basically have scalars, arrays, and hashes. If you are already in Perl land, that’s what you are manipulating.

    You don’t change a JSON key in Perl; you change a hash key. It doesn’t matter that you got that hash from JSON or will output it as JSON. That’s how you get to @pmqs’s delete:

    $hash->{$new_key} = delete $hash->{$old_key};
    

    Everything beyond that is just input and output.

    The right tool for the right job

    You have a few other questions about the output, and those questions are unrelated to changing a hash key. When I deal with this, I simply ignore it. If it outputs it all on one line, I don’t really care. I can get pretty output with jq:

    % jq .
    

    If I want to minify it, I can use testing:

    % jq -r tostring data.json
    

    You mentioned that you didn’t like the indention. That’s easy to change. jq uses 2 space indents, but I can make that smaller (or larger, or tabs) (see also How to restrict indentation while using jq filters on json file):

    % jq --indent 1 . data.json
    

    You say that you can’t use jq because you are using Perl, but remember, Perl is the glue language of the internet. You can easily call other programs to do your work and you are expected to do so when something else can do the job better. Why wrestle with Perl and its modules when an existing tool already does a better job? If you aren’t able to install it for whatever reason, that’s fine. But saying that you can only choose one tool unnecessarily limits you.

    Setting aside that, JSON::PP has settings for space_before, space_after, indent, and indent_length. You can adjust those how you like. If you have problems with those, you can ask a separate question.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search