Remove hyphens from keys in deeply nested map - groovy

I posted this question in the Groovy mailing lists, but I've not yet gotten an answer. I was wondering if someone can help here. I am re-posting relevant text from my original question.
I have an input json that’s nested, that is read via a JsonSlurper, and some of the keys have hyphens in them. I need to replace those keys that have hyphens with underscores and convert it back to json for downstream processing. I looked at the JsonGenerator.Options documentation and I could not find any documentation for this specific requirement.
I also looked through options to iterate through the Map that is produced from JsonSlurper, but unfortunately I’m not able to find an effective solution that iterates through a nested Map, changes the keys and produces another Map which could be converted to a Json string.
Example Code
import groovy.json.*
// This json can be nested many levels deep
def inputJson = """{
"database-servers": {
"dc-1": [
"server1",
"server2"
]
},
"discovery-servers": {
"dc-3": [
"discovery-server1",
"discovery-server2"
]
}
}
"""
I need to convert the above to json that looks like the example below. I can iterate through and convert using the collectEntries method which only works on the first level, but I need to do it recursively, since the input json can be an nested many levels deep.
{
"database_servers": {
"dc_1": [
"server1",
"server2"
]
},
"discovery_servers": {
"dc_3": [
"discovery-server1",
"discovery-server2"
]
}
}

Seems like you just need a recursive method to process the slurped Map and its sub-Maps.
import groovy.json.JsonSlurper
JsonSlurper slurper = new JsonSlurper()
def jsonmap = slurper.parseText( inputJson )
Map recurseMap( def inputMap ) {
return inputMap.collectEntries { key, val ->
String newkey = key.replace( "-", "_" )
if ( val instanceof Map ) {
return [ newkey, recurseMap( val ) ]
}
return [ newkey, val ]
}
}
def retmap = recurseMap( jsonmap )
println retmap // at this point you can use output this however you like

Related

How can I replace a key:value pair by its value wherever the chosen key occurs in a deeply nested dictionary?

Other questions
This is a spin-off from How can I replace a key-value pair in a nested dictionary with the value from the same key-value pair? where the answer is working only in a one-time-nested dictionary.
And it is a spin-off from Loop through all nested dictionary values? which I could not get to work on this problem.
Before:
I have a dictionary that is nested many times.
dict_nested = {
"key_":{
"key0a":{
"key1a":{
"sub_key2a":"sub_value2a",
"sub_key2b":"sub_value2b"},
"key1b":"value1b"},
"key0b":{
"key_XYZ":{
"key1a":{
"sub_key2a":"sub_value2a",
"sub_key2b":"sub_value2b"},
"key1b":"value1b"}
}
}
}
After:
The result should look like this:
dict_nested_new = {
"key_":{
"key0a":{
"sub_key2a":"sub_value2a",
"sub_key2b":"sub_value2b",
"key1b":"value1b"},
"key0b":{
"key_XYZ":{
"sub_key2a":"sub_value2a",
"sub_key2b":"sub_value2b",
"key1b":"value1b"}
}
}
}
Modifying a Python dict while iterating over it
When I looped through the items of the dictionary to delete / replace, I got the error
RuntimeError: dictionary changed size during iteration
which needs somehow to be avoided.
How can I replace the "key1a":SOME_VALUE key-value pair with its value each time it occurs somewhere in the dictionary?
As I understand it, you want to recursively search for a key in a nested dict and promote its value.
This might not be super efficient, but it should work. It also does not really explore dictionaries with lists as values but your example data does not have them so I did not implement that.
import copy
import json
def find_replace(this_dict, target_key):
## optional depending on if you care that you mutate this_dict
this_dict = copy.deepcopy(this_dict)
for key in this_dict:
# if the current value is a dict, dive into it
if isinstance(this_dict[key], dict):
this_dict[key] = find_replace(this_dict[key], target_key)
# if the current key is our target merge the values and remove the key
if key == target_key:
this_dict = {**this_dict, **this_dict[key]}
del this_dict[key]
return this_dict
dict_nested = {
"key_":{
"key0a":{
"key1a":{
"sub_key2a":"sub_value2a",
"sub_key2b":"sub_value2b"
},
"key1b":"value1b"
},
"key0b":{
"key_XYZ":{
"key1a":{
"sub_key2a":"sub_value2a",
"sub_key2b":"sub_value2b",
"key1a": {
"sub_key3a":"sub_value3a",
"sub_key3b":"sub_value3b"
},
},
"key1b":"value1b"
}
}
}
}
dict_nested_new = find_replace(dict_nested, "key1a")
print(json.dumps(dict_nested_new, indent=4))
Should give you:
{
"key_": {
"key0a": {
"key1b": "value1b",
"sub_key2a": "sub_value2a",
"sub_key2b": "sub_value2b"
},
"key0b": {
"key_XYZ": {
"key1b": "value1b",
"sub_key2a": "sub_value2a",
"sub_key2b": "sub_value2b",
"sub_key3a": "sub_value3a",
"sub_key3b": "sub_value3b"
}
}
}
}
Note that I added an addition level of nesting with a sub-nested key match just to show that scenario. Additional optimizations like support for lists and avoiding updates to unchanged keys available for a reasonable fee :-P

Groovy map to json list of objects

I'm trying to take a very simple map of objects and produce a list of objects like so. I have this working, but surely there must be a better way with Groovy?
private def createConfigJson(Map configMap) {
def jsonBuilder = new StringBuilder().append("{\n")
configMap.each { key, value ->
jsonBuilder.append(" \"$key\": \"$value\",\n")
}
// Delete last ',' instead of the newline
jsonBuilder.deleteCharAt(jsonBuilder.length() - 2)
jsonBuilder.append("}")
}
createConfigJson([test: 'test', test2: 'test2'])
will produce:
{
"test": "test",
"test2": "test2"
}
to serialize map to json object (string)
you can use
http://docs.groovy-lang.org/latest/html/gapi/groovy/json/JsonBuilder.html
import groovy.json.JsonBuilder
new JsonBuilder([test: 'test', test2: 'test2']).toPrettyString()
or
http://docs.groovy-lang.org/latest/html/gapi/groovy/json/JsonOutput.html
import groovy.json.JsonOutput
JsonOutput.prettyPrint(JsonOutput.toJson([test: 'test', test2: 'test2']))

Convert WebService Response into Json Arrary and Jsobobject using Groovy

I am testing RESTful webservice using SoapUI. We use Groovy for that.
I am using jsonslurper to parse the response as Object type.
Our reponse is similar to this:
{
"language":[
{
"result":"PASS",
"name":"ENGLISH",
"fromAndToDate":null
},
{
"result":"FAIL",
"name":"MATHS",
"fromAndToDate": {
"from":"02/09/2016",
"end":"02/09/2016"
}
},
{
"result":"PASS",
"name":"PHYSICS",
"fromAndToDate":null
}
]
}
After this, I stuck up on how to.
Get Array (because this is array (starts with -language)
How to get value from this each array cell by passing the key (I should get the value of result key, if name='MATHS' only.)
I could do it using Java, but as just now learning Groovy I could not understand this. We have different keys with same names.
You can just parse it in to a map, then use standard groovy functions:
def response = '''{
"language":[
{"result":"PASS","name":"ENGLISH","fromAndToDate":null},
{"result":"FAIL","name":"MATHS","fromAndToDate":{"from":"02/09/2016","end":"02/09/2016"}},
{"result":"PASS","name":"PHYSICS","fromAndToDate":null}
]
}'''
import groovy.json.*
// Parse the Json string
def parsed = new JsonSlurper().parseText(response)
// Get the value of "languages" (the list of results)
def listOfCourses = parsed.language
// For this list of results, find the one where name equals 'MATHS'
def maths = listOfCourses.find { it.name == 'MATHS' }

ConfigSlurper bad result with nested values with same key

I'm trying to use the Groovy's ConfigSlurper in a projetct but it happens to have a weird behaviour when the same key appears two times in the configuration. Here is an example :
def struct = """
node2 {
subnode21="Test1"
}
root1 {
node2 {
subnode1="Test2"
subnode2=node2.subnode21
}
}
"""
def config = new ConfigSlurper().parse(struct)
This will produce the following result :
[
node2:[
subnode21:Test1
],
root1:[
node2:[
subnode1:Test2,
subnode21:[:],
subnode2:[:]
]
]
]
ConfigSlurper seems to consider that subnode2=node2.subnode21 in the root1 closure refers to root1.node2 and not the node2 closure declared above.
Is this a known limitation or am I missing something ?

Multiple key search in CouchDB

Given the following object structure:
{
key1: "...",
key2: "...",
data: "..."
}
Is there any way to get this object from a CouchDB by quering both key1 and key2 without setting up two different views (one for each key) like:
select * from ... where key1=123 or key2=123
Kind regards,
Artjom
edit:
Here is a better description of the problem:
The object described above is a serialized game state. A game has exactly one creator user (key1) and his opponent (key2). For a given user I would like to get all games where he is involved (both as creator and opponent).
Emit both keys (or only one if equal):
function(doc) {
if (doc.hasOwnProperty('key1')) {
emit(doc.key1, 1);
}
if (doc.hasOwnProperty('key2') && doc.key1 !== doc.key2) {
emit(doc.key2, 1);
}
}
Query with (properly url-encoded):
?include_docs=true&key=123
or with multiple values:
?include_docs=true&keys=[123,567,...]
UPDATE: updated to query multiple values with a single query.
You could create a CouchDB view which produces output such as:
["key1", 111],
["key1", 123],
["key2", 111],
["key2", 123],
etc.
It is very simple to write a map view in javascript:
function(doc) {
emit(["key1", doc["key1"]], null);
emit(["key2", doc["key2"]], null);
}
When querying, you can query using multiple keys:
{"keys": [["key1", 123], ["key2", 123]]}
You can send that JSON as the data in a POST to the view. Or preferably use an API for your programming language. The results of this query will be each row in the view that matches either key. So, every document which matches on both key1 and key2 will return two rows in the view results.
I also was struggling with simular question, how to use
"select * from ... where key1=123 or key2=123".
The following view would allow you to lookup customer documents by the LastName or FirstName fields:
function(doc) {
if (doc.Type == "customer") {
emit(doc.LastName, {FirstName: doc.FirstName, Address: doc.Address});
emit(doc.FirstName, {LastName: doc.LastName, Address: doc.Address});
}
}
I am using this for a web service that queries all my docs and returns every doc that matches both the existence of a node and the query. In this example I am using the node 'detail' for the search. If you would like to search a different node, you need to specify.
This is my first Stack Overflow post, so I hope I can help someone out :)
***Python Code
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import httplib, json
from tornado.options import define,options
define("port", default=8000, help="run on the given port", type=int)
class MainHandler(tornado.web.RequestHandler):
def get(self):
db_host = 'YOUR_COUCHDB_SERVER'
db_port = 5984
db_name = 'YOUR_COUCHDB_DATABASE'
node = self.get_argument('node',None)
query = self.get_argument('query',None)
cleared = None
cleared = 1 if node else self.write('You have not supplied an object node.<br>')
cleared = 2 if query else self.write('You have not supplied a query string.<br>')
if cleared is 2:
uri = ''.join(['/', db_name, '/', '_design/keysearch/_view/' + node + '/?startkey="' + query + '"&endkey="' + query + '\u9999"'])
connection = httplib.HTTPConnection(db_host, db_port)
headers = {"Accept": "application/json"}
connection.request("GET", uri, None, headers)
response = connection.getresponse()
self.write(json.dumps(json.loads(response.read()), sort_keys=True, indent=4))
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler)
]
settings = dict(
debug = True
)
tornado.web.Application.__init__(self, handlers, **settings)
def main():
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
main()
***CouchDB Design View
{
"_id": "_design/keysearch",
"language": "javascript",
"views": {
"detail": {
"map": "function(doc) { var docs = doc['detail'].match(/[A-Za-z0-9]+/g); if(docs) { for(var each in docs) { emit(docs[each],doc); } } }"
}
}
}

Resources