0

I have the following JSON:

{"name":"Guillaume","age":33,"address":"main st","pets":[{"type":"dog", "color":"brown"},{"type":"dog", "color":"brown"}]}}

I have used JsonSlurper to parse it. I have a need to be able to modify the contents of the JSON based on various criteria. The keys I want to modify are externally defined.

I can easily change a string value as follows. The below results in the address field in the lazyMap being changed from "main st" to "second st".

import groovy.json.JsonSlurper
def slurper = new JsonSlurper()  
def result = slurper.parseText('{"person": {"name":"Guillaume","age":33,"address":"main st","pets":[{"type":"dog", "color":"brown"},{"type":"dog", "color":"brown"}]}}')
String address = "result.person.address"  // Note: this is externalized
String newAddressValue = "second st"
Eval.me('result', result, "$address = '$newAddressValue'")
println result.person.address

The problem I can't seem to solve is if I want to change the address value from a string to a map.

import groovy.json.JsonSlurper
def slurper = new JsonSlurper()  
def result = slurper.parseText('{"person": {"name":"Guillaume","age":33,"address":"main st","pets":[{"type":"dog", "color":"brown"},{"type":"dog", "color":"brown"}]}}')

Map newAddressMap = slurper.parseText(/{"street":"Third Street", "city":"New York", "state":"New York"}/)

Eval.me('result', result, "$address = $newAddressMap")
println result.person.address.getClass()
println result.person.address

The $newAddressMap above is interpreted as a string resulting in the following error:

startup failed: Script1.groovy: 1: The current scope already contains a variable of the name York @ line 1, column 51. s = [city:New York, state:New York, stre

However, the below works (changes the address key value from a String to a LazyMap), but requires my key to be known/hard-coded:

result.person.address = newAddressMap
println result.person.address.getClass()
println result.person.address

The below does not error, but the $newAddressMap is a string and the lazyMap key "address" remains a string.

Eval.me('result', result, "$address = '$newAddressMap'")
println result.person.address.getClass()
println result.person.address

How can I change the address key value from a String to a Map while having the address key value defined at runtime?

Mel
  • 5,837
  • 10
  • 37
  • 42
vjanzaldi
  • 1
  • 1

1 Answers1

0

Do you mean like this?

import groovy.json.*

def slurper = new JsonSlurper()
// Given your document
def document = slurper.parseText('{"person": {"name":"Guillaume","age":33,"address":"main st","pets":[{"type":"dog", "color":"brown"},{"type":"dog", "color":"brown"}]}}')
// And your replacement
def newAddressMap = slurper.parseText('{"street":"Third Street", "city":"New York", "state":"New York"}')

// And the location of the attribute you want to replace
def addressPath = 'person.address'

// Split the path, and replace the property with the new map (this mutates document)
addressPath.split('\\.').with {
    def parent = it[0..-2].inject(document) { d, k -> d."$k" }
    parent[it[-1]] = newAddressMap
}    

println new JsonBuilder(document).toPrettyString()

Which prints:

{
    "person": {
        "address": {
            "city": "New York",
            "state": "New York",
            "street": "Third Street"
        },
        "age": 33,
        "name": "Guillaume",
        "pets": [
            {
                "color": "brown",
                "type": "dog"
            },
            {
                "color": "brown",
                "type": "dog"
            }
        ]
    }
}
tim_yates
  • 167,322
  • 27
  • 342
  • 338
  • This is an interesting approach. My actual json is nested and has lists. The fields I want to change can vary greatly. For example, I need to be able to find and change the following predefined placeholders in my json: activity[1].person[2].address and activity[3].person[0].relative[1].address. Doing something like Eval.me('result', result, "$address = '$newAddressValue'") is attractive since I don't have to walk the json to find the field, however it seems limited to just changing the string value. – vjanzaldi Mar 21 '16 at 17:30
  • Where are these placeholders coming from? Eval is quite a security risk if they're use inputs, and if they're not, maybe a closure for each would be better? Also, putting this requirement in the question would have saved us both some time – tim_yates Mar 21 '16 at 19:53
  • Thanks for your response. I was hoping to figure out why I could not assign the map using eval. The placeholders are derived from searching through an instance of the json and search for configured fields like person addresses or relative addresses. Each instance of the json may or may not have the fields. So they are program derived and not user provided. I'm fairly new to groovy and really appreciate your help. Can you provide an example of what you mean by a closure for each placeholder? Your input helped to implement a solution, so thank you. – vjanzaldi Mar 22 '16 at 02:04