philk
philk

Reputation: 2140

Replace all "TRUE" and "FALSE" values in a json with their respective boolean value

I have this input JSON

[{
   "Name":"Wolfenstein",
   "24 Hour":"FALSE",
   "Shop 1":"TRUE",
   "Shop 2":"FALSE",
}]

and want to change all "FALSE" and "TRUE" values to false and true respectively.

[{
   "Name":"Wolfenstein",
   "24 Hour":false,
   "Shop 1":true,
   "Shop 2":false,
}]

As a bonus, I only want to select those array items whose keys start with "Shop" and have at least on "Shop" set to "TRUE". What would be the jq filter to use?

Upvotes: 0

Views: 3618

Answers (2)

Andrew Savinykh
Andrew Savinykh

Reputation: 26270

The following code will replace string "TRUE" with Boolean true and you can pipe that to a similar expression for "FALSE":

.[][] |= if . == "TRUE" then true end

To select the items that have keys starting with "Shop" and have at least one of their value set to true you can pipe the result to:

map(select(with_entries(select((.key | startswith("Shop")) and .value))!={}))

Or all combined:

.[][] |= if . == "TRUE" then true end 
  | .[][] |= if . == "FALSE" then false end 
  | map(select(with_entries(select((.key | startswith("Shop")) and .value))!={}))

The inner select here selects only those properties from the object, with key starting with "Shop" and with truthy value. We are assuming here, that you do not have values other than "TRUE" and "FALSE" for these properties to start with. If any such properties found the resulting object with_entries operated on will not be empty, so we can use that as the condition for the outer select - this will result in objects that we are not interested in being skipped. Finally, map which is a synonym for [.[]|f] is used so that we get a proper json array back, and not a stream of multiple json objects.

Upvotes: 0

peak
peak

Reputation: 116690

Your overall requirements are unclear, but you might want to consider using walk if you really want to update an arbitrary JSON text in the manner you suggest. For simplicity, though, I'll assume you simply have an array of objects of the kind you show.

Let's start with the simple task of changing TRUE/FALSE to true/false. This could be accomplished directly as follows:

map( map_values(if . == "TRUE" then true 
                elif . == "FALSE" then false
                else .
                end) )

But because of your 'at least one "Shop" set to "TRUE"' requirement, it will be helpful to define an auxiliary function:

 def toboolean:
   if . == "TRUE" then true 
   elif . == "FALSE" then false
   else .
   end;

So the first task can be accomplished by:

map(map_values(toboolean))

Now we're good to go. Assuming your jq has any/2, and under one interpretation of your overall requirements, we could write:

map( if any( to_entries[];
             (.key|startswith("Shop")) and .value=="TRUE" )
     then map_values(toboolean)
     else .
     end)

Or if only the "Shop" values are to be altered:

map( if any( to_entries[];
             (.key|startswith("Shop")) and .value=="TRUE" )
     then with_entries( if .key|startswith("Shop")
                        then .value |= toboolean
                        else .
                        end)
     else .
     end)

If your jq does not have any/2, then please consider upgrading; if that is not an option, you could write your own (inefficient) version using reduce.

Using when/2

The solutions above can be streamlined using the generic function when/2, defined as:

def when(filter; action): if filter//null then action else . end;

For example, the eight-line solution above becomes this four-liner, which might also be easier to read once one becomes familiar with when/2:

map( when( any( to_entries[];
                (.key|startswith("Shop")) and .value=="TRUE" );
     with_entries( when( .key|startswith("Shop");
                         .value |= toboolean) ) ))

Upvotes: 2

Related Questions