Transform and flatten JSON using GSON - json

I’m working on splitting a SignalK JSON object into canonical JSON items representing each value.
The original JSON looks like this:
{
"mmsi": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"name": "Mona",
"navigation": {
"position": {
"timestamp": "1991-09-03T03:5:36.000Z",
"latitude": 51.763691,
"longitude": 9.501367,
"altitude": 0.000000,
"source": "N0183-01"
},
"courseOverGroundTrue": {
"value": 23.000000
},
"speedOverGround": {
"value": 2.010289
}
},
"environment": {
"depth": {
"belowTransducer": {
"value": 12.700000
}
},
"wind": {
"angleApparent": {
"value": 0.174533
},
"speedApparent": {
"value": 0.000000
}
}}}
The needed transformed JSON looks like this, with JSON elements representing each value, and with item naming representing the whole path of the value.
{
"items": [{
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "EnvironmentWindSpeedApparent",
"isStep": false,
"name": "EnvironmentWindSpeedApparent",
"timestamps": 1523962903470,
"type": "numerical",
"values": 0.0
},
"key": "20180417-130143470EnvironmentWindSpeedApparent5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "EnvironmentWindAngleApparent",
"isStep": false,
"name": "EnvironmentWindAngleApparent",
"timestamps": 1523962903470,
"type": "numerical",
"values": 0.174533
},
"key": "20180417-130143470EnvironmentWindAngleApparent5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "EnvironmentDepthBelowTransducer",
"isStep": false,
"name": "EnvironmentDepthBelowTransducer",
"timestamps": 1523962903470,
"type": "numerical",
"values": 12.7
},
"key": "20180417-130143470EnvironmentDepthBelowTransducer5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "NavigationPositionLongitude",
"isStep": false,
"name": "NavigationPositionLongitude",
"timestamps": 1523962903470,
"type": "numerical",
"values": 9.501367
},
"key": "20180417-130143470NavigationPositionLongitude5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "NavigationPositionLatitude",
"isStep": false,
"name": "NavigationPositionLatitude",
"timestamps": 1523962903470,
"type": "numerical",
"values": 51.763691
},
"key": "20180417-130143470NavigationPositionLatitude5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "NavigationCourseOverGroundTrue",
"isStep": false,
"name": "NavigationCourseOverGroundTrue",
"timestamps": 1523962903470,
"type": "numerical",
"values": 23.0
},
"key": "20180417-130143470NavigationCourseOverGroundTrue5377770-4ee4-4a4b-3230-888037332031"
}, {
"columns": {
"assetId": "urn:mrn:signalk:uuid:5377770-4ee4-4a4b-3230-888037332031",
"description": "NavigationSpeedOverGround",
"isStep": false,
"name": "NavigationSpeedOverGround",
"timestamps": 1523962903470,
"type": "numerical",
"values": 2.010289
},
"key": "20180417-130143470NavigationSpeedOverGround5377770-4ee4-4a4b-3230-888037332031"
}
]}
How to do this transformation in a flexible way that adopts to changing sub-nodes being available in the original JSON ?
I’m transforming it now in a simplistic way, but would like to know if it can be done using JsonReader , gson or other ways of iterating through the original JSON object.

I ended up defining the object structure representing the transformed json, and writing a custom serializer to do the transformation. It could be more efficient ways of doing it, but this approach seems to be working OK.
public class SignalKDeserializer implements JsonDeserializer<TargetObject> {
//written based on examples from http://www.javacreed.com/gson-deserialiser-example/
final TargetObject targetObject = new TargetObject ();
#Override
public TargetObject deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
traverse (jsonObject,0,"", mmsi);
return targetObject;
}
private void traverse (JsonObject jsonObject, Integer level, String parentName, String mmsi) {
Set<String> keys = jsonObject.keySet();
Iterator<?> keysIterator = keys.iterator ();
while( keysIterator.hasNext ()) {
String key = (String) keysIterator.next ();
String signalName = parentName+upperCaseFirst (key); //setting signalName to complete path of value
if (jsonObject.get (key) instanceof JsonObject) {
traverse ((jsonObject.get (key)).getAsJsonObject (),level+1,upperCaseFirst (signalName),mmsi);
} else if (jsonObject.get (key) instanceof JsonElement) {
if (level>0) {
try {
final Double value = jsonObject.get(key).getAsDouble ();
calendar = Calendar.getInstance ();
Long timeStamp = calendar.getTimeInMillis ();
Item targetItem = new Item ();
targetItem.columns.setIsStep (false);
targetItem.columns.setAssetId (mmsi);
targetItem.columns.setTimestamps (calendar.getTimeInMillis ());
targetItem.columns.setType ("numerical");
targetItem.columns.setDescription (signalName);
targetItem.columns.setValues (value);
targetItem.columns.setName (signalName);
targetItem.setKey (signaldateformat.format (timeStamp) + mmsi);
targetObject.items.add (targetItem);
}
catch (NumberFormatException n) {
// Expected, the value is non numerical and will not be transformed
}
}
}
}
}}
I'm using the serializer from my main class like this:
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(TargetObject.class, new SignalKDeserializer ());
final Gson gson = gsonBuilder.create();
TargetObject targetObject = gson.fromJson (jsonSignalK,TargetObject.class);
String jsonOutString = gson.toJson (targetObject);
jsonOutString contains the transformed json I needed.

Related

Flutter: parsing json response that has multiple keys

I'm trying to get the values of these keys from this json response:
{
"pro": {
"groups": [
"1": {
"name": "Base",
"fields": [
{
"id": 3,
"value": {
"raw": "Name",
}
},
{
"id": 4,
"value": {
"raw": "avatar",
}
},
]
},
"2": {
"name": "Base",
"fields": [
{
"id": 6,
"value": {
"raw": "Name",
}
},
{
"id": 7,
"value": {
"raw": "avatar",
}
},
]
}
]
}
}
I could get the values "name": "Base"
json['pro']['groups']["1"]['name'],
But I can't get the values of key "raw".
How can I get the values of key "raw"?
The values of fields are a list, so you will get a list of raw values:
List<String> raw = json['pro']['groups']['1']['fields'].map((v) => v['value']['raw'];
Also, it seems like groups is an array but as an object? then you can do something like this:
List<String> raw = [];
Map<String, dynamic> groups = json['pro']['groups'];
for (var key in groups.keys) {
raw.add(groups[key]['fields'].map((v) => v['value']['raw']);
}
or
List<String> raw = groups.keys.map((key) => groups[key]['fields'].map((v) => v['value']['raw']);
I haven't tested the code, but hopefully, it works as expected :)
first thing first. your json is invalid.
try to paste your json here it will show why is your json is invalid
after you fix the json the new structure json will be looked like this
{
"pro": {
"groups": [
{
"name": "Base",
"fields": [
{
"id": 3,
"value": {
"raw": "Name",
}
},
{
"id": 4,
"value": {
"raw": "avatar",
}
},
]
},
{
"name": "Base",
"fields": [
{
"id": 6,
"value": {
"raw": "Name",
}
},
{
"id": 7,
"value": {
"raw": "avatar",
}
},
]
}
]
}
}
and then in order to grab the value of raw, you have to parse the json first using jsonDecode(), then you can use something like this:
Map<String, dynamic> groupOne = json['pro']['groups'][0];
Map<String, dynamic> groupOneFieldOne = groupOne['fields'][0];
print(groupOneFieldOne['value']['raw']);
but that's just an example. if you want to access them easily you can use .map() like this:
List<Map<String, dynamic>> groups = json['pro']['groups'];
groups.map(
(Map<String, dynamic> group) => (group['fields'] as List<dynamic>).map(
(dynamic field) => field['value']['raw'],
),
);
that's it! if you want to ask anything just put a comment ;)
you can copy and paste on dartpad
List<Map<String, dynamic>> groups = json['pro']['groups'];
print(groups.map(
(Map<String, dynamic> group) => (group['fields'] as List<dynamic>).map(
(dynamic field) => field['value']['raw'],
),
));
}
Map<String, dynamic> json = {
"pro": {
"groups": [
{
"name": "Base",
"fields": [
{
"id": 3,
"value": {
"raw": "Name",
}
},
{
"id": 4,
"value": {
"raw": "avatar",
}
},
]
},
{
"name": "Base",
"fields": [
{
"id": 6,
"value": {
"raw": "Name",
}
},
{
"id": 7,
"value": {
"raw": "avatar",
}
},
]
}
]
}
};

springfox swagger configuration for JsonNode property in a model class

I have a Model 'NewModel' with the following property
import com.fasterxml.jackson.databind.JsonNode;
#ApiModel(
description = "Data Class to hold new details"
)
public class NewModel {
#ApiModelProperty(
notes = "Value in JSON key:value format. Can be any key:value pair",
example = "{ds:2017:08:05,hh:11}"
)
private final JsonNode value;
(... getters and setters ...)
}
Apart from this, I have some rest controllers which get a JSON in request body. I use this model to get the JSOn from request body.
I have configured springfox swagger using maven, and generated the api definition. But in the generated API definitions, this model has been generated as
"NewModel": {
"type": "object",
"properties": {
"value": {
"example": "{nds:2017:08:05,hh:11}",
"description": "Value of the stamp in JSON key:value format",
"$ref": "#/definitions/JsonNode"
}
},
"description": "Data Class to hold details"
}
And the reference JsonNode definition generated is
"definitions": {
"JsonNode": {
"type": "object",
"properties": {
"array": {
"type": "boolean"
},
"bigDecimal": {
"type": "boolean"
},
"bigInteger": {
"type": "boolean"
},
"binary": {
"type": "boolean"
},
"boolean": {
"type": "boolean"
},
"containerNode": {
"type": "boolean"
},
"double": {
"type": "boolean"
},
"float": {
"type": "boolean"
},
"floatingPointNumber": {
"type": "boolean"
},
"int": {
"type": "boolean"
},
"integralNumber": {
"type": "boolean"
},
"long": {
"type": "boolean"
},
"missingNode": {
"type": "boolean"
},
"nodeType": {
"type": "string",
"enum": [
"ARRAY",
"BINARY",
"BOOLEAN",
"MISSING",
"NULL",
"NUMBER",
"OBJECT",
"POJO",
"STRING"
]
},
"null": {
"type": "boolean"
},
"number": {
"type": "boolean"
},
"object": {
"type": "boolean"
},
"pojo": {
"type": "boolean"
},
"short": {
"type": "boolean"
},
"textual": {
"type": "boolean"
},
"valueNode": {
"type": "boolean"
}
}
}
Now, when I generate a client library with this API definition, the JsonNode Object allowed on the client side has only Boolean variables, and I cannot assign actual JSON strings to it, and hence cannot pass a JSON value to the connecting server (from which I generated the API definitions)
I was wondering is there was a way I could get to pass Json Strings from client to server using the swagger generated libraries. Or any other directions in which I can achieve the required end result.
Thanks (and apologies for the long post)
An attribute in ApiModelProperty, dataType="java.util.Map" should help
public class NewModel {
#ApiModelProperty(
example = "{ds:2017:08:05,hh:11}",
dataType = "java.util.Map"
)
private final JsonNode value;
Perhaps AlternateTypeRuleConvention, which is introduced in Springfox, will help you.
For example, you can do this:
#Bean
public AlternateTypeRuleConvention typeConvention(final TypeResolver resolver) {
return new AlternateTypeRuleConvention() {
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
#Override
public List<AlternateTypeRule> rules() {
return Collections.singletonList(
AlternateTypeRules.newRule(resolver.resolve(JsonNode.class), resolver.resolve(jsonNodeApi()))
);
}
};
}
private Type jsonNodeApi() {
return new AlternateTypeBuilder()
.fullyQualifiedClassName(
String.format("%s.generated.%s",
JsonNode.class.getPackage().getName(),
JsonNode.class.getSimpleName()))
.build();
}
AlternateTypeBuilder also allows you to specify custom fields if necessary.

How to get key from ArrayList nested in JSON using Groovy and change its value

I need to be able to find the key quote.orderAttributes[0].attributeDetail.name and set its value to null or any other value I want. I only need to do this for the first element in any list so selecting [0] is fine. I want to be able to use a path such as 'quote.orderAttributes.attributeDetail.name'. But given the amount of time I've spent so far, please advise of any better approaches.
Here is the Json:
{
"source": "source",
"orderId": null,
"Version": null,
"quote": {
"globalTransactionId": "k2o4-6969-1fie-poef",
"quoteStatus": "Not Uploaded",
"events": {
"eventDescription": "event description",
"eventTypeName": "Event Type"
},
"someReport": {
"acceptResultsFlag": "Y",
"orderDate": "2017-06-14",
"orderStatus": "string"
},
"anotherReport": {
"id": 627311,
"orderDate": "2017-06-14"
},
"attributes": [
{
"appliedFlag": "Y",
"attributeDetail": {
"name": "name1",
"value": "value1"
},
"attributeName": "attribute1"
},
{
"appliedFlag": "N",
"attributeDetail": {
"name": "name2",
"value": "value2"
},
"attributeName": "attribute2"
}
],
"orderAttributes": [
{
"appliedFlag": "Y",
"attributeDetail": {
"name": "name3",
"value": "value3"
},
"attributeName": "orderAttribute1"
},
{
"appliedFlag": "N",
"attributeDetail": {
"name": "name4",
"value": "value4"
},
"attributeName": "orderAttribute2"
}
]
}
}
I know the following works but requires that I know which object(s) is an ArrayList and specify its [0] indexed item:
def input = new File("src/test/resources/ShortExample.json")
def json = new JsonSlurper().parse(input)
def option1 = json['quote']["attributes"][0]["attributeDetail"]["name"]
println option1
//or this
//where csvData.fullPath = quote.orderAttributes.attributeDetail.name
def (tkn1, tkn2, tkn3, tkn4) = csvData.fullPath.tokenize('.')
def option2 = json["$tkn1"]["$tkn2"][0]["$tkn3"]["$tkn4"]
println option2
I would like to be able to:
def input = new File("src/test/resources/ShortExample.json")
def json = new JsonSlurper().parse(input)
def changeValueTo = null
def (tkn1, tkn2, tkn3, tkn4) = csvData.fullPath.tokenize('.')
json["$tkn1"]["$tkn2"]["$tkn3"]["$tkn4"] = changeValueTo
I've tried to implement many of the examples on here using recursion, methods creating MapsOrCollections that identify what the object is and then search it for key or value, even trampoline examples.
If you could point me to a good article explaining serialization and deserialization it would be much appreciated too.
Thank you in advance.
as variant:
import groovy.json.*;
def json = '''{
"source": "source",
"orderId": null,
"Version": null,
"quote": {
"globalTransactionId": "k2o4-6969-1fie-poef",
"quoteStatus": "Not Uploaded",
"attributes": [
{
"appliedFlag": "Y",
"attributeDetail": {
"name": "name1",
"value": "value1"
},
"attributeName": "attribute1"
},
{
"appliedFlag": "N",
"attributeDetail": {
"name": "name2",
"value": "value2"
},
"attributeName": "attribute2"
}
]}
}'''
json = new JsonSlurper().parseText(json)
def jsonx(Object json, String expr){
return Eval.me('ROOT',json, expr)
}
println jsonx(json, 'ROOT.quote.attributes[0].attributeDetail.name')
jsonx(json, 'ROOT.quote.attributes[0].attributeDetail.name = null')
println jsonx(json, 'ROOT.quote.attributes[0].attributeDetail.name')
You can access and modify any nested field of JSON object directly, e.g.
json.quote.attributes[0].attributeDetail.name = null
This is possible, because new JsonSlurper().parse(input) returns a groovy.json.internal.LazyMap object. Groovy allows you to access and modify any Map entries using dot notation, e.g.
Map<String, Map<String, Integer>> map = [
lorem: [ipsum: 1, dolor: 2, sit: 3]
]
println map.lorem.ipsum // Prints '1'
map.lorem.ipsum = 10
println map.lorem.ipsum // Prints '10'
You can apply same approach to your example, e.g.
import groovy.json.JsonSlurper
String input = '''{
"source": "source",
"orderId": null,
"Version": null,
"quote": {
"globalTransactionId": "k2o4-6969-1fie-poef",
"quoteStatus": "Not Uploaded",
"events": {
"eventDescription": "event description",
"eventTypeName": "Event Type"
},
"someReport": {
"acceptResultsFlag": "Y",
"orderDate": "2017-06-14",
"orderStatus": "string"
},
"anotherReport": {
"id": 627311,
"orderDate": "2017-06-14"
},
"attributes": [
{
"appliedFlag": "Y",
"attributeDetail": {
"name": "name1",
"value": "value1"
},
"attributeName": "attribute1"
},
{
"appliedFlag": "N",
"attributeDetail": {
"name": "name2",
"value": "value2"
},
"attributeName": "attribute2"
}
],
"orderAttributes": [
{
"appliedFlag": "Y",
"attributeDetail": {
"name": "name3",
"value": "value3"
},
"attributeName": "orderAttribute1"
},
{
"appliedFlag": "N",
"attributeDetail": {
"name": "name4",
"value": "value4"
},
"attributeName": "orderAttribute2"
}
]
}
}'''
def json = new JsonSlurper().parse(input.bytes)
assert json.quote.attributes[0].attributeDetail.name == 'name1'
json.quote.attributes[0].attributeDetail.name = null
assert json.quote.attributes[0].attributeDetail.name == null
I hope it helps.

Elasticsearch NEST 2.x Field Names

I am upgrading to NEST 2.3.0 and trying to rewrite all queries that were originally written for NEST 1.x.
I am using the Couchbase transport plugin that pushes data from Couchbase to Elasticsearch.
POCO
public class Park
{
public Park()
{
}
public bool IsPublic { get; set; }
}
Mapping is like this
"mappings": {
"park": {
"_source": {
"includes": [
"doc.*"
],
"excludes": [
"meta.*"
]
},
"properties": {
"meta": {
"properties": {
"rev": {
"type": "string"
},
"flags": {
"type": "long"
},
"expiration": {
"type": "long"
},
"id": {
"type": "string",
"index": "not_analyzed"
}
}
},
"doc": {
"properties": {
"isPublic": {
"type": "boolean"
}
}
}
}
}
}
Sample document in elasticsearch
{
"_index": "parkindex-local-01",
"_type": "park",
"_id": "park_GUID",
"_source": {
"meta": {
"expiration": 0,
"flags": 33554433,
"id": "park_GUID",
"rev": "1-1441a2c278100bc00000000002000001"
},
"doc": {
"isPublic": true,
"id": "park_GUID"
}
}
}
My query in NEST
var termQuery = Query<Park>.Term(p => p.IsPublic, true);
ISearchResponse<T> searchResponse = this.client.Search<T>(s => s.Index("parkindex-local-01")
.Take(size)
.Source(false)
.Query(q => termQuery));
This query goes to Elasticsearch as below
{
"size": 10,
"_source": {
"exclude": [
"*"
]
},
"query": {
"term": {
"isPublic": {
"value": "true"
}
}
}
}
It doesn't retrieve any data, it will work only if I prefix the field name with "doc." so query becomes as below
{
"size": 10,
"_source": {
"exclude": [
"*"
]
},
"query": {
"term": {
"doc.isPublic": {
"value": "true"
}
}
}
}
How do I write the query above in NEST so it can properly interpret the field names, I tried using Nested Query with Path set to "doc", but that gave an error saying field is not of a nested type.
Do I need to change my mapping?
This all used to work in Elasticsearch 1.x and NEST 1.x, I guess this has to do with breaking changes to field names constraints.
Fields can no longer be referenced by shortnames in Elasticsearch 2.0.
isPublic is a property of the doc field, which is mapped as an object type, so referencing by the full path to the property is the correct thing to do.
NEST 2.x has some ways to help with field inference, an example
public class Park
{
public Doc Doc { get; set;}
}
public class Doc
{
public bool IsPublic { get; set;}
}
var termQuery = Query<Park>.Term(p => p.Doc.IsPublic, true);
client.Search<Park>(s => s.Index("parkindex-local-01")
.Take(10)
.Source(false)
.Query(q => termQuery));
results in
{
"size": 10,
"_source": {
"exclude": [
"*"
]
},
"query": {
"term": {
"doc.isPublic": {
"value": true
}
}
}
}
You may also want to take a look at the automapping documentation.

Linq to Json using Like Clause

I've got an MVC 3 web app and am returning a JSON object which I would like to use Linq against to limit the result returned to the client jquery.
The Json response takes the following structure:
{
"Data": {
"Items": [
{
"Result": {
"Id": "e2ba4912-c387-4f54-b55e-06742a6858db",
"Name": "SomeOtherSetting",
"Value": "500",
"Archived": false
},
"Result": {
"Id": "17c27584-cea8-42c2-b6c4-0b30625ac3ce",
"Name": "Setting2",
"Value": "600",
"Archived": false
},
"Result": {
"Id": "17c27584-cea8-42c2-b6c4-0b30625ac3ce",
"Name": "Setting3",
"Value": "700",
"Archived": false
}
}]
}
}
....
I need to return or grab just the json items that have a Name like 'Setting' for example. In the example, this would return just the 2 Json nodes.
My Linq is very limited and what I have is: Settings is where the Json response is stored.
NewtonSoft.Json.Linq.JObject data = NewtonSoft.Json.Linq.JObject.Parse(Settings);
var result = from p in data["Data"]["Items"].Children()
where (p["Result"]["Name"].Contains("Expenses.Help.Tip"))
select new { Name = (string)p["Result"]["Name"], Value = (string)p["Result"]["Value"] };
When I run this I get nothing in my result. Can anyone help and tell me what I'm doing wrong?
Thanks.
Well, I'm not a Json specialist, but I think your Json structure has some problems.
I tried an online parser, and parsing took only the third result... (try to copy past your code in the left window, and look at JS eval.
Instead of
"Items": [
{
"Result": {
},
"Result": {
},
"Result": {
}
}]
you should have each element of Items array (each 'Result') into {}.
"Items": [
{
{"Result": {
}
},
{"Result": {
}
},
{"Result": {
}
}]
I got it to work by changing your Json file to
{
"Data": {
"Items": [
{
"Result": {
"Id": "e2ba4912-c387-4f54-b55e-06742a6858db",
"Name": "SomeOtherSetting",
"Value": "500",
"Archived": false
}
},
{
"Result": {
"Id": "17c27584-cea8-42c2-b6c4-0b30625ac3ce",
"Name": "Setting2",
"Value": "600",
"Archived": false
}
},
{
"Result": {
"Id": "17c27584-cea8-42c2-b6c4-0b30625ac3ce",
"Name": "Setting3",
"Value": "700",
"Archived": false
}
}]
}
}
using
var data = JObject.Parse(test)["Data"]["Items"].Children()
.Where(m => m["Result"]["Name"].Value<string>().Contains("Setting"))
.Select(m => new
{
Name = m["Result"]["Name"].Value<string>(),
Value = m["Result"]["Value"].Value<int>()
})
.ToList();