in reply to Re^2: Find a Position and Insert Text
in thread Find a Position and Insert Text

I totally understand. I remember the same boat quite well. That said, the solution, or at least its style, I gave is the only sane way to approach your problem and by far the easiest. Regular expressions can indeed move mountains but it's much harder than you're thinking and for your problem I doubt you (or anyone for that matter) would ever arrive at anything that worked except in the most trivial and inflexible case.

My code was idiomatic but it is easy and straightforward once you can follow it. It's also robust whereas a regex solution will never be. I'll adapt the code a bit to make it more clear and walk you through it. <readmore/> away…

You have a regular data format: JSON. Never use regular expressions when you have a known format with good engines at your disposal. The JSON modules for Perl are tops. You will likely have to install one. JSON::XS is my choice. You can install things even as a beginner and even as a non-admin: Yes, even you can use CPAN. Related: I highly recommend cpanm.

The JSON module(s) turn text into data (read JSON and create Perl data structures) and turn data into text (write Perl data to JSON strings). This is often called marshalling, text -> data, and serializing, data -> text (well, some storage format).

decode_json takes a string of JSON text and returns a Perl data structure. E.g.,

use JSON; my $json = '{ "o": "hai", "hurr": "derp" }'; my $data = decode_json( $json ); print $data->{o}, $/; # Prints "hai"

Reverse it,

print encode_json($data), $/; # Prints: {"hurr":"derp","o":"hai"}

Notice that the JSON Object is not in the same order as the original text and the spacing is different. This is because order (for Objects, not Arrays of course) and whitespace are not relevant. Do not think otherwise. Data formats work a billion times better when they are strictly respected. And this is part of why a regular expression approach here is fail.

# Some plain Perl- my @stuf = ( 1, 2, 3 ); # Or, idiomatically ( 1..3 ) print encode_json(\@stuf), $/; # Need \ to take reference. # Same outcome as [] for array ref plus 1..3- print encode_json( [ 1 .. 3 ] ), $/;

This is a simplistic version of your data. I replaced nulls with values to make example code easier to understand.

{"OWNER":"KeyProjects","age1":"eon","ht1":"moo","wc":"EF1"}

Your requirement appears to be merely to get the age and ht fields into completed and scheduled sub-structures. Simplifying again for brevity here we'll only use scheduled. Means from the simplistic data above, we want this-

{"OWNER":"KeyProjects","scheduled":{"age1":"eon","ht1":"moo"},"wc":"EF +1"}

Code should be pretty easy to follow and, more importantly, to play with now-

use warnings; use strict; use JSON; my $original = '{"OWNER":"KeyProjects","age1":"eon","ht1":"moo"}'; my $desired = '{"OWNER":"KeyProjects","scheduled":{"age1":"eon","ht1" +:"moo"}}'; my $data = decode_json($original); print "The owner is; ", $data->{OWNER}, $/; print "age1 is: ", $data->{age1}, $/; my $age = delete $data->{age1}; print "age1 is now: ", $data->{age1}, $/; # Empty, we deleted it... # ...while putting it into- print "Here I am: $age\n"; # \n = $/, usually/sort-of. my $ht = delete $data->{ht1}; my %sub_hash = ( ht1 => $ht, age1 => $age ); # Perl auto-vivivies data- $data->{scheduled} = \%sub_hash; # \% is like {} print encode_json( $data ), $/; # {"scheduled":{"age1":"eon","ht1":"moo"},"OWNER":"KeyProjects"} # Remember, order is irrelevant in Objects.

That meets your spec. Picking which keys was entirely manual though, so not practical. This is where we go back to the regular expression I used in the first sample: qr/\A (?: age\d+ | ht\d+ ) \z/x;

qr creates a regular expression. The x flag on a regular expression means whitespace doesn't count. \A means start of string, \z means the end. (?:) is non-capturing group and () would work the same here. We group it so we can | (OR) two things between the start and end: age (followed by at least one number) and same for ht. Could also write it (age|ht)\d+.

Now we use the regular expression to decide which keys and their values we'll move to the substructure. Instead of doing them manually like above. I put the nulls back, just because.

use warnings; use strict; use JSON; my $original = '{"OWNER":"KeyProjects","age1":null,"ht1":null}'; my $desired = '{"OWNER":"KeyProjects","scheduled":{"age1":null,"ht1": +null}}'; my $data = decode_json($original); for my $key ( keys %{ $data } ) { print "Found a key: $key", $/; if ( $key =~ /\A (?: age\d+ | ht\d+ ) \z/x ) { print " -> It matches our regular expression\n"; print " -> Deleting it from data structure\n"; my $value = delete $data->{$key}; print " -> Putting it back under sub-structure with key 'sche +duled'\n"; $data->{scheduled}{$key} = $value; } else { print " -> It's fine where it is, no action taken\n"; } } print encode_json( $data ), $/;

Epilog: the Test::More stuff before was just compare the data programmatically so you don't have to eyeball it and hope. To do that here, add on-

use Test::More; my $desired_data = decode_json( $desired ); is_deeply( $desired_data, $data, "Data structures match" ); done_testing(1);

Have fun, HTH, TTFN, etc. :P

Further Reading

Update: </c></c> fixed.

Replies are listed 'Best First'.
Re^4: Find a Position and Insert Text
by Corion (Patriarch) on Mar 17, 2016 at 07:49 UTC

    In the original data, age1 occurs two times (with potentially two different values), while in your approach, both entries will share the same value. I don't know if that is important for the OP though.

      Good eye++. It is the same issue as Perl hashes though. Duplicate Object keys in JS are a mistake and will be clobbered.

      <script type="text/javascript"> var p = { "age1":"one", "age1":"two" }; for ( var key in p ) { if ( p.hasOwnProperty(key) ) { document.write(key + " -> " + p[key] + "<br/>"); } } </script> age1 -> two
Re^4: Find a Position and Insert Text
by jlb333333 (Novice) on Mar 19, 2016 at 11:24 UTC

    Thank you for your time and effort with your reply

    I really appreciate it.

    Below is just your code with some comments/questions etc.

    Would appreciate your comments at some stage.

    I am really still learning Perl.

    use warnings; use strict; use JSON; use Data::Dumper; my $original = '{"OWNER":"KeyProjects","age1":null,"ht1":null}';

    #Literal entry.

    my $desired  = '{"OWNER":"KeyProjects","scheduled":{"age1":null,"ht1":null}}';

    #Literal entry.

    my $data = decode_json($original); print Dumper $original; print Dumper $desired; print Dumper $data; for my $key ( keys % {$data} )

    #Converts $data into a hash and cycles through it.

    { print "Found a key: $key", $/; if ( $key =~ /\A (?: age\d+ | ht\d+ ) \z/x ) { print " -> It matches our regular expression\n"; print " -> Deleting it from data structure\n"; my $value = delete $data->{$key};

    #Does this delete the pair, the value, or what? See below.

    print Dumper $key;

    #Looks like nothing is deleted.

    print Dumper $value;

    #Looks like nothing is deleted.

    print " -> Putting it back under sub-structure with key 'sche +duled'\n"; $data->{scheduled}{$key} = $value;

    #I should understand this step but I don't really know what each aspect of this line is doing.

    print Dumper $key; print Dumper $value; print Dumper $data; } else { print " -> It's fine where it is, no action taken\n"; print Dumper $key; print Dumper $data; } } print encode_json( $data ), $/; use Test::More; my $desired_data = decode_json( $desired ); is_deeply( $desired_data, $data, "Data structures match" ); done_testing(1);

      I’m glad it’s got you thinking. Here’s a little more to play with. The only part it answers directly is the delete function. I think you will start to understand more the more you play around with the code. Part of what makes Perl so easy/fun is how easy it is to test ideas, adjust and run. Remember, you have great built-in documentation. Just run perldoc -f delete for functions for example. Or perldoc perltoc or perldoc Data::Dumper, etc… :P

      use strict; use warnings; use Data::Dumper; $Data::Dumper::Terse = 1; # Raw structures, no $VAR1 style names. # One hash, with a (hash) reference taken to it– my %stuff = ( somekey => "someval", o => "hai" ); my $stuff_ref = \%stuff; # reference literally *references* original. print "Same hash, two ways to access: $stuff{o} eq $stuff_ref->{o}\n"; # See the hash right now. print "Original hash: ", Dumper( $stuff_ref ); my $value = delete $stuff_ref->{o}; print "Hash after deleting key o: ", Dumper( $stuff_ref ); print "Print it directly (uninitialized now): o => ", $stuff_ref->{o}, + $/; # Add a new dimension to the data– $stuff_ref->{new_key}{sub_key} = "new value"; # Give it a friend, old key/value, new position– $stuff_ref->{new_key}{o} = $value; print "Hash after moving stuff around: ", Dumper( $stuff_ref ); $stuff_ref->{as}{deep}{as}{you} = "please"; $stuff_ref->{as}{deep}{as}{array_ref} = [ "one", "two" ]; $stuff_ref->{subroutine} = sub { print "OHAI!\n" }; $stuff_ref->{fancy_data} = [ "one", { two => "is a hash(ref)" }, ["ano +ther","array"], "last" ]; print "Perl data structures are *flexible* ", Dumper( $stuff_ref ); print "Excecuting code ref through data..."; $stuff_ref->{subroutine}->();
      (Update: I'm not missing <ins>you</ins> at all… now.)

        Thank you, again

        So interesting

        Before I knew about what data in MongoDB looked like, I looked at a complex structure of hashes and arrays and thought, if one could set up a structure and had a way of inserting data into it easily, then it would be a visibly useful way of storing data. Low and behold, it seems to be the case.

        Not that I have any knowledge or ability concerning MongoDB, hashes, or other stuff like them.

        That's how I started looking at Mongo a few weeks ago. It's fascinating. I've taught myself a little sql. I'm looking for a reason to adapt my sql data to use it in Mongo, but I can't at present see any valid reason for doing that. Pity.

        Thank you very much for your help with this.

        I understand:

        my $value = delete $stuff_ref->{o};

        and your data structure examples, and manipulating them, better now.