How to parse variable child nodes with Gson? - json

Apologies for the confusing title, could not come up with a better one.
My API returns a response like this :
{
"type":"a",
"extras":{
"a1":"valueA1",
"a2":"valueA2"
}
}
If the type is "b", then response is like :
{
"extras":{
"b1":"valueB1"
},
"type":"b" //note that "type" may come after "extras"
}
For a third type "c", response can be :
{
"type":"c",
"extras":{
"b1":"valueB1",
"a1":"valueA1",
"a2":"valueA2"
}
}
Following are my requirements :
There are over 20 such "types" which the API can return.
Each type has an extra node with attributes which may or may not be shared with other typeExtras. Typically 1-10 attributes in each extra node.
Order of "type" and "extras" is not fixed. But both are guaranteed to be present.
The naive solution I am trying is to have one "extras" POJO with all possible attribute values :
Extras.java
String a1;
String a2;
String b1;
My clients have checks like this during consumption:
if(type.isA()) {
a1 = extras.a1;
a2 = extras.a2;
//there is no way to prevent developers from calling extras.b1
}
My code is bloated with over 50 attributes and littered with null checks for 20 types. Is there a better way ?
Bonus question : I strongly feel that the API is poorly designed. Are there any second thoughts ?

Related

Solving this simple(?) JSONPath

I am new to this topic, but I hope you can help me. I can't figure out a correct JSON expression for solving my problem.
Given JSON structure (coming from zigbee2mqtt):
{
"message" : "announce",
"meta" : {
"friendly_name" : "Lamp1"
},
"type" : "device_announced"
}
What I am trying:
if $.type == 'device_announced' then return the friendly_name
which in this case is
Lamp1
If I understand correctly, you are looking for an expression like this:
$[?(#.type=='device_announced')].meta.friendly_name
So, we are filtering the root collection on types equal to the search string, and then drill down to the friendly_name. You can test this online here.
Note: Some implementations require you to wrap your JSON in an array [ ] to allow this kind of filtering.

Creating assertions using Groovy where JSON contains multiple objects with the same name

I'm currently writing some tests in ReadyAPI to test a new API that we are building. The API will return JSON in the following format:
{
"testJSON" : [
{
"test1" : "111",
"test2" : "ABC"
},
{
"test1" : "222",
"test2" : "DEF"
}
]
}
I have added the following Groovy code that breaks down the JSON and lists each value:
import groovy.json.JsonSlurper
def jsonResp = context.expand( '${GetTest#Response}' )
def jsonElements = new JsonSlurper().parseText(jsonResp)
for ( value in jsonElements.testInput ) {
value.each{
log.info "${it.key}:${it.value}"
}
}
What I am trying to do is to take this JSON response and compare it to a pre-defined list of data (using a Data Source within ReadyAPI). This data source will contain a column titled 'test1' and a column titled 'test2' -these will contain all the values expected for each object. So we would need to ensure that each test1 value from the datasource is displayed in the response JSON. And we'd also like to confirm that for each 'test1' we get the correct corresponding 'test2'.
So in example above, we'd want to prove that the response contained 'test1' values of 111 and 222, and that for 111 we got a 'test2' value of ABC etc.
The example above is a simplistic view of our new API and in reality it will contain a much larger number of fields and a lot more responses. So ideally, I don't want to have to hard code specific values within the Groovy script - which is why I'm trying to use a data sheet.
Has anyone had any experience of this sort of thing and can make any suggestions?
Thanks in advance for your help, please let me know if you need any more information.
Cheers,
Matt
"testJSON" is a list of objects containing properties "list1" and "list2".
Your datasource is also a list of objects (rows) containing properties (columns) "list1" and "list2".
Use a nested for loop to compare each expected object with each object in the response. Use a boolean flag to track whether test1 is missing from the response. When test1 is found, check the value of test2.
Here's the basic outline. I'm assuming test1 is a unique identifier for each object. You may also want to exit the inner loop when you do find test1, to be more efficient.
for (element in datasource) {
def foundTest1 = false
for (response in testJSON) {
if (response.test1 == element.test1){
foundTest1 = true
assert response.test2 == element.test2
// assert any number of other properties here
}
}
assert foundTest1 == true
}

Return nested JSON in AWS AppSync query

I'm quite new to AppSync (and GraphQL), in general, but I'm running into a strange issue when hooking up resolvers to our DynamoDB tables. Specifically, we have a nested Map structure for one of our item's attributes that is arbitrarily constructed (its complexity and form depends on the type of parent item) — a little something like this:
"item" : {
"name": "something",
"country": "somewhere",
"data" : {
"nest-level-1a": {
"attr1a" : "foo",
"attr1b" : "bar",
"nest-level-2" : {
"attr2a": "something else",
"attr2b": [
"some list element",
"and another, for good measure"
]
}
}
},
"cardType": "someType"
}
Our accompanying GraphQL type is the following:
type Item {
name: String!
country: String!
cardType: String!
data: AWSJSON! ## note: it was originally String!
}
When we query the item we get the following response:
{
"data": {
"genericItemQuery": {
"name": "info/en/usa/bra/visa",
"country": "USA:BRA",
"cardType": "visa",
"data": "{\"tourist\":{\"reqs\":{\"sourceURL\":\"https://travel.state.gov/content/passports/en/country/brazil.html\",\"visaFree\":false,\"type\":\"eVisa required\",\"stayLimit\":\"30 days from date of entry\"},\"pages\":\"One page per stamp required\"}}"
}}}
The problem is we can't seem to get the Item.data field resolver to return a JSON object (even when we attach a separate field-level resolver to it on top of the general Query resolver). It always returns a String and, weirdly, if we change the expected field type to String!, the response will replace all : in data with =. We've tried everything with our response resolvers, including suggestions like How return JSON object from DynamoDB with appsync?, but we're completely stuck at this point.
Our current response resolver for our query has been reverted back to the standard response after none of the suggestions in the aforementioned post worked:
## 'Before' response mapping template on genericItemQuery query; same result as the 'After' listed below **
#set($result = $ctx.result)
#set($result.data = $util.parseJson($ctx.result.data))
$util.toJson($result)
## 'After' response mapping template **
$util.toJson($ctx.result)
We're trying to avoid a situation where we need to include supporting types for each nest level in data (since it changes based on parent Item type and in cases like the example I gave it can have three or four tiers), and we thought changing the schema type to AWSJSON! would do the trick. I'm beginning to worry there's no way to get around rebuilding our base schema, though. Any suggestions to the contrary would be helpful!
P.S. I've noticed in the CloudWatch logs that the appropriate JSON response exists under the context.result.data response field, but somehow there's the following transformedTemplate (which, again, I find very unusual considering we're not applying any mapping template except to transform the result into valid JSON):
"arn": ...
"transformedTemplate": "{data={tourist={reqs={sourceURL=https://travel.state.gov/content/passports/en/country/brazil.html, visaFree=false, type=eVisa required, stayLimit=30 days from date of entry}, pages=One page per stamp required}}, resIds=USA:BRA, cardType=visa, id=info/en/usa/bra/visa}",
"context": ...
Apologies for the lengthy question, but I'm stumped.
AWSJSON is a JSON string type so you will always get back a string value (this is what your type definition must adhere to).
You could try to make a type for data field which contains all possible fields and then resolve fields to a corresponding to a parent type or alternatively you could try to implement graphQL interfaces

How to get the key name in json?

My previous problem was I'm unable to arrange the json structure like what I wanted. And I found some answers that looks like it almost satisfy my needs but unfortunately I don't know if it's working or not because another problem has occurred.
Below, I arranged my own json data based on the json structure by someone named Programmer.
{
"dialog_type": {"human": {"inner": "He is so scary"}}
}
Here, I have a key called "human". I have two keys in my data. First is "human" and second is "non_human". Now if I have two data in my json file, it will become like this :
{
"dialog_type": {"human": {"inner": "He is so scary"}}
},
{
"dialog_type": {"non_human": "Once upon a time..."}
}
This case is maybe simillar to someone asked here. But unfortunately I have no idea if it's possible to do that in unity. I want to make a method like this answer. So I can determine what action to take by comparing those keys.
Now the question is how do I get the key name as a string in my json data using C# ?
To access the property names of a Unity javascript object, you can use:
for(var property in obj) {}
For instance, this will log all keys (i.e. property names) of all the property key-value pairs in a Unity javascript object (e.g. "key1" and "key2"):
function Start () {
var testObject = {
"key1": "value 1",
"key2": "value 2"
};
for(var property in testObject) {
Debug.Log(property.Key);
};
}
That should give you a way to check objects for any matching property names you are interested in.

Is this json badly constructed?

The following json text is the result of a call to eBay's search API.
Q: Is this json badly constructed? (It's technically, correct - so that's not what I'm referring to)
By that I mean, notice how -every value- is inside an array? instead of just being a "key" : "value" ?
eg.
"ack": [
"Success"
],
or
"version": [
"1.12.0"
],
etc..
Now, before you answer "well, maybe each key has multiple result values" .. I'm pretty sure most of them can't.
(this json schema is really making my life a pita. yes, it's easy to make each POCO property a List<string> but it's the point of it all!)
References: Here's the office eBay API documention for this endpoint
After reading the documentation, I understand eBay's approach, but I find it poor in terms of client side deserialisation. For example, the benefit of an array value for the ack property is that the value could also contain a warning, e.g.:
{
"ack": [
"Success",
"Warning"
]
}
However, a list of strings is not ideal for client side processing (e.g. in C#, bool hasWarning = pocoList.Contains("Warning"); doesn't strike me as completely foolproof). I'd rather have a response such as:
{
"ack": {
"result": "Success",
"warning": null
}
}
Then with my deserialised POCO, given that the value of warning is still a string, I could write this:
[DataContract]
public class Ack
{
[DataMember(Name="result")]
public string Result { get; set; }
[DataMember(Name="warning")]
public string Warning { get; set; }
[IgnoreDataMember]
public bool HasWarning
{
get { return !string.IsNullOrEmpty(Warning); }
}
}
This would allow me to replace my previous LINQ query with bool hasWarning = ack.HasWarning;.
There are definitely places where use of arrays is completely unnecessary. The docs describe the version property as "the release version that eBay used to process the request", thus I would return this as a single string. The array would only make sense if it was a versions property (e.g. for identifying all versions of the backend that support a specific request).
I've definitely seen worse JSON responses, but there are places where the APIs should ideally be returning a JSON object or a single value.
I normally use this tool for see if a JSON is good constructed:
http://json.parser.online.fr/
In your case, is not correct. You should store the data by Keys and Values.