Recursively removing whitespace from JSON field names in Groovy - json

I have a Groovy process that is receiving troublesome JSON that has attribute/field names containing whitespaces:
{
"leg bone" : false,
"connected to the" : {
"arm bones " : [
{
" fizz" : "buzz",
"well hello" : "there"
}
]
}
}
Above, fields such as "leg bone" and "well hello" are causing issues during processing (even though they are technically legal JSON fields). So I want to scan each field (recursively, or in nested fashion) in the incoming JSON and string replace any whitespace with an underscore ("_"). Hence, the above JSON would be converted into:
{
"leg_bone" : false,
"connected_to__the" : {
"arm_bones_" : [
{
"_fizz" : "buzz",
"well_hello" : "there"
}
]
}
}
Typically I use a JsonSlurper for parsing JSON strings into maps, but I can't seem to figure out how to get the recursion correct. Here's my best attempt so far:
// In reality 'incomingJson' isn't hardcoded as a string literal, but this helps make my actual use case
// an SSCCE.
class JsonMapExperiments {
static void main(String[] args) {
String incomingJson = """
{
"leg bone" : false,
"connected to the" : {
"arm bones " : [
{
" fizz" : "buzz",
"well hello" : "there"
}
]
}
}
"""
String fixedJson = fixWhitespaces(new JsonSlurper().parseText(incomingJson))
println fixedJson
}
static String fixWhitespaces(def jsonMap) {
def fixedMap = [:]
String regex = ""
jsonMap.each { key, value ->
String fixedKey = key.replaceAll('\\s+', '_')
String fixedValue
if(value in Map) {
fixedValue = fixWhitespaces(value)
} else {
fixedValue = value
}
fixedMap[fixedKey] = fixedValue
}
new JsonBuilder(fixedMap).toString()
}
}
When this runs, the final output is:
{"connected_to_the":"{\"arm_bones_\":\"[{ fizz=buzz, well hello=there}]\"}","leg_bone":"false"}
Which is kinda/sorta close, but not exactly what I need. Any ideas?

Given your input and this script:
def fixWhitespacesInTree(def tree) {
switch (tree) {
case Map:
return tree.collectEntries { k, v ->
[(k.replaceAll('\\s+', '_')):fixWhitespacesInTree(v)]
}
case Collection:
return tree.collect { e -> fixWhitespacesInTree(e) }
default :
return tree
}
}
def fixWhitespacesInJson(def jsonString) {
def tree = new JsonSlurper().parseText(jsonString)
def fixedTree = fixWhitespacesInTree(tree)
new JsonBuilder(fixedTree).toString()
}
println fixWhitespacesInJson(json)
I got the following results:
{"connected_to_the":{"arm_bones_":[{"_fizz":"buzz","well_hello":"there"}]},"leg_bone":false}
I would, however, suggest that you change the regular expression \\s+ to just \\s. In the former case. if you have two JSON properties at the same level, one called " fizz" and the other called " fizz" then the translated keys will both be "_fizz" and one will overwrite the other in the final result. In the latter case, the translated keys will be "_fizz" and "__fizz" respectively, and the original content will be preserved.

Related

Newtonsoft.json SelectToken Replace differs from SelectTokens Replace in foreach with a NullReferenceException

Hope anybody could guide me here. I spend some hours on it and can't understand what's going on.
Mission: Replace a json element by a jsonpath search tag. (sort of $ref feature)
In my code example below i want to replace the value of DataReaderUser by a value found by the json path search $.UsersAndGroups.Users[?(#.Name == 'OMDASAccountUser')].Username . In this case it should result in the value "contoso\SVCSCOM-DO-OMDAS"
The code below works as expected.. the issue is below this code ..
https://dotnetfiddle.net/gEjggK
using System;
using Newtonsoft.Json.Linq;
public class Program
{
public static void Main()
{
string json = #"{
""SQLServer"": {
""SQLReportingServices"": {
""AccountSettings"": {
""DataReaderUser"": {""$JsonPath"": ""$.UsersAndGroups.Users[?(#.Name == 'OMDASAccountUser')].Username""},
}
}
},
""UsersAndGroups"": {
""Users"": [
{
""Name"": ""OMActionAccountUser"",
""Username"": ""contoso\\SVCSCOM-DO-OMDAS"",
},
{
""Name"": ""OMDASAccountUser"",
""Username"": ""contoso\\SVCSCOM-DO-OMDAS"",
}
]
}
}";
JObject jo = JObject.Parse(json);
var JsonPath = jo.SelectToken("..$JsonPath");
JsonPath.Parent.Parent.Replace(jo.SelectToken(JsonPath.ToString()));
Console.WriteLine(jo.ToString());
}
}
The output will be :
{
"SQLServer": {
"SQLReportingServices": {
"AccountSettings": {
"DataReaderUser": "contoso\\SVCSCOM-DO-OMDAS"
}
}
},
"UsersAndGroups": {
"Users": [
{
"Name": "OMActionAccountUser",
"Username": "contoso\\SVCSCOM-DO-OMDAS"
},
{
"Name": "OMDASAccountUser",
"Username": "contoso\\SVCSCOM-DO-OMDAS"
}
]
}
}
Now the issue:
I want to do the same for all possible jsonpaths refers. So i use the SelectTokens and an foreach . But it looks like the behavior is different , the parents are null.
https://dotnetfiddle.net/lZW3XP
using System;
using Newtonsoft.Json.Linq;
public class Program
{
public static void Main()
{
string json = #"{
""SQLServer"": {
""SQLReportingServices"": {
""AccountSettings"": {
""DataReaderUser"": {""$JsonPath"": ""$.UsersAndGroups.Users[?(#.Name == 'OMDASAccountUser')].Username""},
}
}
},
""UsersAndGroups"": {
""Users"": [
{
""Name"": ""OMActionAccountUser"",
""Username"": ""contoso\\SVCSCOM-DO-OMDAS"",
},
{
""Name"": ""OMDASAccountUser"",
""Username"": ""contoso\\SVCSCOM-DO-OMDAS"",
}
]
}
}";
JObject jo = JObject.Parse(json);
var JsonPaths = jo.SelectTokens("..$JsonPath");
foreach (var JsonPath in JsonPaths )
{
JsonPath.Parent.Parent.Replace(jo.SelectToken(JsonPath.ToString()));
}
Console.WriteLine(jo.ToString());
}
}
And the output:
Run-time exception (line 34): Object reference not set to an instance of an object.
Stack Trace:
[System.NullReferenceException: Object reference not set to an instance of an object.]
at Newtonsoft.Json.Linq.JsonPath.PathFilter.GetNextScanValue(JToken originalParent, JToken container, JToken value)
at Newtonsoft.Json.Linq.JsonPath.ScanFilter.<ExecuteFilter>d__4.MoveNext()
at Program.Main() :line 34
would be great to get some directions since i am spinning my head here.
michel
SelectTokens uses lazy evaluation and if you modify the token while enumerating all matches it can break in unexpected ways. A simple fix is to add ToArray() to force eager evaluation:
var JsonPaths = jo.SelectTokens("..$JsonPath").ToArray();

Tornadofx REST client

I have followed an example shown here
link
And i got the hang of it, i managed to create my own "Employee" entity and i found some dummy api data online to play with.
like this Problem is, the tornadofx throws null pointer error, and i think its because the rest response sends something like this
{
"status": "success",
"data": [
{
"id": "1",
"employee_name": "Tiger Nixon",
"employee_salary": "320800",
"employee_age": "61",
"profile_image": ""
},
but when i use mocky and provide JUST the json part
[
{
"id": "1",
"employee_name": "Tiger Nixon",
"employee_salary": "320800",
"employee_age": "61",
"profile_image": ""
},...]
it all works fine.
I think those additional fields "status" and "success" in response confuse the rest client of tornadofx, and i cant manage to get it to work, is there anyway to tell client to ignore every other fields besides those of json data.
All links are functional, so you can try yourself.
full working example
package com.example.demo.view
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
import javafx.scene.layout.BorderPane
import tornadofx.*
import javax.json.JsonObject
class Employee (id:Int?=null , name: String? = null, age: Int?=null): JsonModel {
val idProperty = SimpleIntegerProperty(this, "id")
var id by idProperty
val ageProperty = SimpleIntegerProperty(this, "age")
var age by ageProperty
val employeeNameProperty = SimpleStringProperty(this, "name", name)
var name by employeeNameProperty
override fun updateModel(json: JsonObject) {
with(json) {
id = int("id")!!
age = int("employee_age")!!
name = string("employee_name")
}
}
override fun toJSON(json: JsonBuilder) {
with(json) {
add("id", id)
add("employee_name", name)
add("employee_age", age)
}
}
}
class PersonEditor : View("Person Editor") {
override val root = BorderPane()
val api : Rest by inject()
var persons = listOf(Employee(1,"John", 44), Employee(2,"Jay", 33)).observable()
val model = PersonModel(Employee())
init {
api.baseURI = "https://run.mocky.io/v3/"
val response = api.get("f17509ba-2d12-4c56-b441-69ab23302e43")
println(response.list())
println(response.list().toModel<Employee>()[0].name)
// print( p.get(1))
with(root) {
center {
tableview(response.list().toModel<Employee>()) {
column("Id", Employee::idProperty)
column("Name", Employee::employeeNameProperty)
column("Age", Employee::ageProperty)
// Update the person inside the view model on selection change
model.rebindOnChange(this) { selectedPerson ->
item = selectedPerson ?: Employee()
}
}
}
right {
form {
fieldset("Edit person") {
field("Id") {
textfield(model.id)
}
field("Name") {
textfield(model.name)
}
field("Age") {
textfield(model.age)
}
button("Save") {
enableWhen(model.dirty)
action {
save()
}
}
button("Reset").action {
model.rollback()
}
}
}
}
}
}
private fun save() {
// Flush changes from the text fields into the model
model.commit()
// The edited person is contained in the model
val person = model.item
// A real application would persist the person here
println("Saving ${person.employeeNameProperty} / ${person.ageProperty}")
}
}
class PersonModel(person: Employee) : ItemViewModel<Employee>(person) {
val id = bind(Employee::idProperty)
val name = bind(Employee::employeeNameProperty)
val age = bind(Employee::ageProperty)
}
if you replace base url and send request to http://dummy.restapiexample.com/api/v1/employees you will get an error that i am talking about
Your call to mocky returns a list, so .list() works fine. Your call to restapiexample, however, returns an object, not a list, so .list() won't do what you expect. You can probably use something like this, though I haven't tested it:
response.one().getJsonArray("data").toModel<Employee>()[0].name)
Further explanation:
If you're not familiar with the structure of JSON, check out the diagrams on the JSON homepage.
TornadoFX has two convenience functions for working with JSON returns: .list() and .one(). The .list() function will check if the result is a JsonArray. If so, it simply returns it. If it is instead a JsonObject, it wraps that object in a list and returns the new list.
In your case, since restapiexample is returning an object, the result of your call to .list() is a JsonArray with a single object. It looks something like this:
[
{
"status": "success",
"data": [...]
}
]
Obviously that single object cannot be converted to an Employee, so dereferencing anything off of it will result in a NullPointerException.
The .one() function on the other hand will check if the response is a JsonObject. If it is, it simply returns the object. If, however, the response is a JsonArray, it will take the first item from the array and return that item.

Getting element value from jsonpath whose root is an array

I have a JSON response which has root as an array of 1 or more objects. I want to extract the value of one of the elements within each object.
Here is the JSON sample:
[
{
"od_pair":"7015400:8727100",
"buckets":[
{
"bucket":"C00",
"original":2,
"available":2
},
{
"bucket":"A01",
"original":76,
"available":0
},
{
"bucket":"B01",
"original":672,
"available":480
}
]
},
{
"od_pair":"7015400:8814001",
"buckets":[
{
"bucket":"C00",
"original":2,
"available":2
},
{
"bucket":"A01",
"original":40,
"available":40
},
{
"bucket":"B01",
"original":672,
"available":672
},
{
"bucket":"B03",
"original":632,
"available":632
},
{
"bucket":"B05",
"original":558,
"available":558
}
]
}
]
I want to access the values of od_pair within each object.
I tried referring to the root array as $ but that did not help.
This is the code snippet I have written:
List<Object> LegList = jsonPath.getList("$");
int NoofLegs = LegList.size();
System.out.println("No of legs :" +NoofLegs);
for (int j=0; j<=NoofLegs;j++) {
String OD_Pair = jsonPath.param("j", j).getString("[j].od_pair");
System.out.println("OD Pair: " + OD_Pair);
List<Object> BucketsList = jsonPath.param("j", j).getList("[j].buckets");
int NoOfBuckets = BucketsList.size();
System.out.println("no of Buckets: " + NoOfBuckets);
}
This is the error that I see:
Caused by:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup
failed:
Script1.groovy: 1: unexpected token: [ # line 1, column 27.
restAssuredJsonRootObject.[j].od_pair
Can someone kindly help me here please?
You were right to start with the $. However, What you get with your particular JSON is List of HashMap<String, Object> where each JSON Object is represented as a single HashMap. Knowing that you can obtain the list of HashMaps like this:
List<HashMap<String, Object>> jsonObjectsInArray = path.getList("$");
The String will be the name of the attribute. The Object will be either String, Integer, JSONObject or JSONArray. The latter isn't exact class names but it's not relevant to you to achieve desired results.
Now, all we have to do is iterate over the HashMap and extract values of od_pair like this:
for (HashMap<String, Object> jsonObject : jsonObjectsInArray) {
System.out.println(jsonObject.get("od_pair"));
}
The output is:
7015400:8727100
7015400:8814001
Hope it helps!

JSONPath expression to get a value from an array on condition or just the first value

Given JSON structured like this:
{
"name":"Some Guy",
"emails":[
{
"description":"primary",
"status":"UNVERIFIED",
"email":"first#first-email.com"
},
{
"description":"home",
"status":"VERIFIED",
"email":"second#second-email.com"
},
{
"description":"away",
"status":"VERIFIED",
"email":"third#third-email.com"
}
]
}
I would like a JSONPath expression to get the first email with status VERIFIED and if there are none, then just get the first email in the array. So, given the example above, the result would be second#second-email.com. Given this example:
{
"name":"Some Guy",
"emails":[
{
"description":"primary",
"status":"UNVERIFIED",
"email":"first#first-email.com"
},
{
"description":"home",
"status":"UNVERIFIED",
"email":"second#second-email.com"
}
]
}
the result would be first#first-email.com.
Is this possible with a JSONPath expression?
You effectively have 2 JSONPath expressions, where the second one (first email) should be evaluated only if the first one (first verified email) returns nothing, so I don't think you can evaluate them both at the same time, in a single expression.
You can apply them one after the other, though:
public static String getEmail(String json) {
Configuration cf = Configuration.builder().options(Option.SUPPRESS_EXCEPTIONS).build();
DocumentContext ctx = JsonPath.using(cf).parse(json);
List<String> emails = ctx.read("$.emails[?(#.status == 'VERIFIED')].email");
if (!emails.isEmpty()) {
return emails.get(0);
}
return ctx.read("$.emails[0].email");
}
If the email array is empty, ctx.read("$.emails[0].email") will return null instead of throwing an exception, thanks to the option SUPPRESS_EXCEPTIONS.
If you don't know the number of paths in advance:
public static String getEmail(String json, String[] paths) {
Configuration cf = Configuration.builder().options(Option.ALWAYS_RETURN_LIST, Option.SUPPRESS_EXCEPTIONS).build();
DocumentContext ctx = JsonPath.using(cf).parse(json);
for (String path : paths) {
List<String> emails = ctx.read(path);
if (!emails.isEmpty()) {
return emails.get(0);
}
}
return null;
}
The option ALWAYS_RETURN_LIST means the return type is a list, even when you have one or zero results.
This code should work perfectly for you
//Use the json parsing library to extract the data
JsonPath jp = new JsonPath(json);
Map<String, Object> location = jp.get("name.emails[0]");
System.out.println(location);

Parsing JSON with Dart

I want to be able to parse a string to an object that I can access using the dot notation e.g. myobject.property, instead of the array notation e.g. myobject['property']. The array notation works fine. Here's what I have so far.
I have some XML:
<level1 name="level1name">
<level2 type="level2Type">
<entry>level2entry</entry>
<entry>level2entry</entry>
</level2>
</level1>
Which converts to the JSON:
{
"level1": {
"name": "level1name",
"level2": {
"type": "level2Type",
"entry": [
"level2entry",
"level2entry"
]
}
}
}
I have the following Dart code:
Object jsonObject = JSON.parse("""{
"level1": {
"name": "level1name",
"level2": {
"type": "level2Type",
"entry": [
"level2entry",
"level2entry"
]
}
}
}
""");
print("my test 1 == ${jsonObject}");
print("my test 2 == ${jsonObject['level1']}");
print("my test 3 == ${jsonObject['level1']['name']}");
which produce the (desired) output:
my test 1 == {level1: {name: level1name, level2: {type: level2Type, entry: [level2entry, level2entry]}}}
my test 2 == {name: level1name, level2: {type: level2Type, entry: [level2entry, level2entry]}}
my test 3 == level1name
But when I try:
print("my test 1 == ${jsonObject.level1}");
I get the following:
Exception: NoSuchMethodException : method not found: 'get:level1'
Receiver: {level1: {name: level1name, level2: {type: level2Type, entry: [level2entry, level2entry]}}}
Arguments: []
Stack Trace: 0. Function: 'Object.noSuchMethod' url: 'bootstrap' line:717 col:3
Ideally, I want an object that I can access using the dot notation and without the compiler giving warning about Object not having property. I tried the following:
class MyJSONObject extends Object{
Level1 _level1;
Level1 get level1() => _level1;
set level1(Level1 s) => _level1 = s;
}
class Level1 {
String _name;
String get name() => _name;
set name(String s) => _name = s;
}
...
MyJSONObject jsonObject = JSON.parse("""{
"level1": {
"name": "level1name",
"level2": {
"type": "level2Type",
"entry": [
"level2entry",
"level2entry"
]
}
}
}
""");
...
print("my test 1 == ${jsonObject.level1.name}");
but instead of giving me 'level1name' as hoped, I get:
Exception: type 'LinkedHashMapImplementation<String, Dynamic>' is not a subtype of type 'MyJSONObject' of 'jsonObject'.
What am I doing wrong here? Is there any way to do what I'm trying? Thanks.
At the moment, JSON.parse only returns Lists (array), Maps, String, num, bool, and null
(api ref).
I suspect that until reflection makes it way into the language, it won't be able to re-construct objects based upon the keys found in json.
You could, however, create a constructor in your MyJsonObject which took a string, called JSON.parse internally, and assigned the various values.
Something like this works in the dart editor:
#import("dart:json");
class Level2 {
var type;
var entry;
}
class Level1 {
var name;
var level2;
}
class MyJSONObject {
Level1 level1;
MyJSONObject(jsonString) {
Map map = JSON.parse(jsonString);
this.level1 = new Level1();
level1.name = map['level1']['name'];
level1.level2 = new Level2();
level1.level2.type = map['level1']['level2']['type'];
//etc...
}
}
main() {
var obj = new MyJSONObject(json);
print(obj.level1.level2.type);
}
A non trivial version would needs some loops and possible recursion if you had deeper nested levels.
Update: I've hacked together a non-trivial version (inspired by the post below), it's up on github (also taking Seth's comments re the constructor):
Chris is completely right. I will only add that the JSON parser could be modified to return a little richer object (something like JsonMap instead of pure Map) that could allow jsonObj.property by implementing noSuchMethod. That would obviously perform worse than jsonObj['property'].