parse complex json from http response - json

I'm doing a http.PostForm against an API and the result is a json.
The Json structure is like this:
{
"sites": [
{
"site_id": 456,
"status": "pending-dns-changes",
"domain": "blabla",
"account_id": 123,
"acceleration_level": "advanced",
"site_creation_date": 1515455285000,
"ips": [
"1.2.3.4"
],
"dns": [
{
"dns_record_name": "something.bla.com",
"set_type_to": "CNAME",
"set_data_to": [
"something.x.incapdns.net"
]
}
],
"original_dns": [
{
"dns_record_name": "blabla2.com",
"set_type_to": "A",
"set_data_to": [
""
]
},
{
"dns_record_name": "blabla3.something.com",
"set_type_to": "A",
"set_data_to": [
"1.2.4.5"
]
},
],
"warnings": [],
"active": "bypass",
"support_all_tls_versions": false,
"use_wildcard_san_instead_of_full_domain_san": true,
"add_naked_domain_san": true,
"additionalErrors": [],
"display_name": "something.blablabla2.com",
"security": {
"waf": {
"rules": [
{
"action": "api.threats.action.alert",
"action_text": "Alert Only",
"id": "api.threats.sql_injection",
"name": "SQL Injection"
},
{
"action": "api.threats.action.alert",
"action_text": "Alert Only",
"id": "api.threats.cross_site_scripting",
"name": "Cross Site Scripting"
},
{
"action": "api.threats.action.alert",
"action_text": "Alert Only",
"id": "api.threats.illegal_resource_access",
"name": "Illegal Resource Access"
},
{
"block_bad_bots": true,
"challenge_suspected_bots": false,
"id": "api.threats.bot_access_control",
"name": "Bot Access Control"
},
{
"activation_mode": "api.threats.ddos.activation_mode.auto",
"activation_mode_text": "Auto",
"ddos_traffic_threshold": 1000,
"id": "api.threats.ddos",
"name": "DDoS"
},
{
"action": "api.threats.action.quarantine_url",
"action_text": "Auto-Quarantine",
"exceptions": [
{
"values": [
{
"urls": [
{
"value": "/neverbogus.pvr",
"pattern": "EQUALS"
}
],
"id": "api.rule_exception_type.url",
"name": "URL"
}
],
"id": 1618746719
},
{
"values": [
{
"urls": [
{
"value": "/doubleneverbogus.pvr",
"pattern": "EQUALS"
},
{
"value": "/ddoubleneverbogus.pvr",
"pattern": "EQUALS"
}
],
"id": "api.rule_exception_type.url",
"name": "URL"
}
],
"id": 856301271
},
{
"values": [
{
"client_apps": [
"537"
],
"id": "api.rule_exception_type.client_app_id",
"name": "Client app ID"
}
],
"id": 58318563
},
{
"values": [
{
"ips": [
"192.168.66.66"
],
"id": "api.rule_exception_type.client_ip",
"name": "IP"
}
],
"id": 707083378
},
{
"values": [
{
"geo": {
"countries": [
"BF"
]
},
"id": "api.rule_exception_type.country",
"name": "Country"
}
],
"id": 1432086237
},
{
"values": [
{
"user_agents": [
"gasdfafafdfasdfadsfadsffads"
],
"id": "api.rule_exception_type.user_agent",
"name": "User agent"
}
],
"id": 1876871261
},
{
"values": [
{
"parameters": [
"bogusparamnamehere234"
],
"id": "api.rule_exception_type.http_parameter",
"name": "Http parameter"
}
],
"id": 1338747790
}
],
"id": "api.threats.backdoor",
"name": "Backdoor Protect"
},
{
"action": "api.threats.action.alert",
"action_text": "Alert Only",
"id": "api.threats.remote_file_inclusion",
"name": "Remote File Inclusion"
},
{
"action": "api.threats.action.disabled",
"action_text": "Ignore",
"id": "api.threats.customRule",
"name": "IncapRules"
}
]
}
},
"sealLocation": {
"id": "api.seal_location.none",
"name": "No seal "
},
"ssl": {
"origin_server": {
"detected": true,
"detectionStatus": "ok"
},
"custom_certificate": {
"active": false
},
"generated_certificate": {
"ca": "GS",
"validation_method": "dns",
"validation_data": [
{
"dns_record_name": "blablablasomething2",
"set_type_to": "TXT",
"set_data_to": [
"somethingtexty"
]
}
],
"san": [
"*.blabla2.com"
],
"validation_status": "done"
}
},
"siteDualFactorSettings": {
"enabled": false,
"customAreas": [],
"customAreasExceptions": [],
"allowAllUsers": true,
"shouldSuggestApplicatons": true,
"allowedMedia": [
"ga",
"sms"
],
"shouldSendLoginNotifications": true,
"version": 0
},
"login_protect": {
"enabled": false,
"specific_users_list": [],
"send_lp_notifications": true,
"allow_all_users": true,
"authentication_methods": [
"ga",
"sms"
],
"urls": [],
"url_patterns": []
},
"performance_configuration": {
"advanced_caching_rules": {
"never_cache_resources": [],
"always_cache_resources": []
},
"acceleration_level": "advanced",
"async_validation": true,
"minify_javascript": true,
"minify_css": true,
"minify_static_html": true,
"compress_jpeg": true,
"compress_jepg": true,
"progressive_image_rendering": false,
"aggressive_compression": false,
"compress_png": true,
"on_the_fly_compression": true,
"tcp_pre_pooling": true,
"comply_no_cache": false,
"comply_vary": false,
"use_shortest_caching": false,
"perfer_last_modified": false,
"prefer_last_modified": false,
"disable_client_side_caching": false,
"cache300x": false,
"cache_headers": []
},
"extended_ddos": 1000000,
"log_level": "security",
"incap_rules": [
{
"id": 51589,
"name": "Wordpress",
"action": "api.rule_action_type.rule_action_alert",
"rule": "(URL contains \"/xmlrpc.php$\" | URL contains \"/wp-login.php$\") & (User-Agent == \"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36\" | User-Agent == \"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1\")",
"creation_date": 1518810206000
},
{
"id": 52179,
"name": "No Browser",
"action": "api.rule_action_type.rule_action_alert",
"rule": "(ClientType != Browser & ClientType != SpamBot & ClientType != DDoSBot & ClientType != SiteHelper) & (User-Agent != \"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534+ (KHTML, like Gecko) BingPreview/1.0b\" & User-Agent != \"Pingdom.com_bot_version_1.4_(http://www.pingdom.com/)\")",
"creation_date": 1519432068000
}
],
"res": 0,
"res_message": "OK",
"debug_info": {
"id-info": "13019"
}
},
API Docs: https://docs.imperva.com/bundle/cloud-application-security/page/api/sites-api.htm#List
This seems to be very painful for me to understand in go as I'm just starting..
From what I've been reading I should just define an object of type struct with my expected format.
Any way I can tackle this a lot easier? I'm interested in extracted for each individual site a few items like site_id / ips / domain.
Unsure how I should tackle this. I've been trying to get my head around https://github.com/buger/jsonparser but I'm failing to actually understand how to use it.
So far what I've been doing is:
func main() {
fmt.Println("Starting this..")
formData := url.Values{
"api_id": {keyid},
"api_key": {apikey},
"page_size": {"100"},
"page_num": {"0"},
}
response, err := http.PostForm("https://my.imperva.com/api/prov/v1/sites/list", formData)
if nil != err {
fmt.Println("Ooops..", err)
}
log.Println(response.Status)
bodyBytes, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
var results map[string]interface{}
fmt.Println(string(bodyBytes))
json.Unmarshal([]byte(bodyBytes), &results)
defer response.Body.Close()
}

If you only need to extract a couple of fields, you don't really need to define an elaborate struct type to describe the whole structure. You can use the stdlib to unmarshal the JSON into a map[string]interface{} and then use something like this function to look for interesting keys:
// findNested looks for a key named s in map m. If values in m map to other
// maps, findNested looks into them recursively. Returns true if found, and
// the value found.
func findNested(m map[string]interface{}, s string) (bool, interface{}) {
// Try to find key s at this level
for k, v := range m {
if k == s {
return true, v
}
}
// Not found on this level, so try to find it nested
for _, v := range m {
nm, ok := v.(map[string]interface{})
if ok {
found, val := findNested(nm, s)
if found {
return found, val
}
}
}
// Not found recursively
return false, nil
}
This code deals only with nested maps. You can adjust it slightly to see through slices, etc.

You can define a single, complex go struct that reflects the entire json object structure, and annotate all of the elements with the json object/element names.
You can define simple structs for each of the nested objects, and annotate these structs with the json object/element names; and then build the complex struct by composing these structs.
Either of these are a ton of effort. You do not need the entire struct, you only need a few elements.
You can either define only the parts of the struct that you need to extract, and annotate those, or you can use the map[string]interface approach you have used. Both approaches work well.
package main
import (
"encoding/json"
"fmt"
)
func main() {
// your http query/response here
// Declare an empty interface
var results map[string]interface{}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
// Unmarshal or Decode the JSON to the interface.
json.Unmarshal([]byte(body), &results)
defer response.Body.Close()
json.Unmarshal([]byte(empJson), &result)
//Reading each value by its key
fmt.Println("site_id:", result["site_id"],
"\ndomain:", result["domain"],
"\nip:", result["ips"][0],
// etc
)
}
See also:
How to unmarshal JSON in to array of interface and use

Related

Flutter - How to read json object with two lists?

I have a json like this:
{
"data": [
{
"id": "99412",
"type": "post_list_item",
"attributes": {
"message": "#:user/3 \n#:user/77 \n#:user/52 #:user/3921 #:user/267 #:user/5 \n\nWhich sector will outperform in 2023 ???",
"visibility": "public_visible",
"created_at": "2023-01-07T14:38:00.691+05:30",
"updated_at": "2023-01-07T14:38:00.779+05:30",
"meta_info": null,
"type": "Post",
"likes_count": 8,
"replies_count": 2,
"widgets_data": [],
"is_liked": false,
"is_bookmarked": false,
"is_deleted": false,
"share_count": null
},
"relationships": {
"user": {
"data": {
"id": "86806",
"type": "user"
}
},
"poll": {
"data": {
"id": "483",
"type": "poll"
}
},
"tagged_users": {
"data": [
{
"id": "3",
"type": "user"
},
{
"id": "5",
"type": "user"
},
{
"id": "52",
"type": "user"
},
{
"id": "77",
"type": "user"
},
{
"id": "267",
"type": "user"
},
{
"id": "3921",
"type": "user"
}
]
},
"tagged_companies": {
"data": []
},
"tagged_topics": {
"data": []
},
"tagged_instruments": {
"data": []
},
"stories": {
"data": []
},
"attachments": {
"data": []
},
"media_files": {
"data": []
}
}
},
{
"id": "99548",
"type": "post_list_item",
"attributes": {
"message": "𝐓𝐨𝐩 𝐧𝐞𝐰𝐬 𝐭𝐨𝐝𝐚𝐲🎌 \n\n📍SGX Nifty up by 132 pts indicating a strong open
today. FIIs sold Rs 29 Bn while DIIs bought Rs 10.8 Bn of stocks on Friday.\n\n📍India surpasses Japan to become world's 3rd largest auto market in 2022: SIAM report\n\n📍Dr Lal PathLabs, leader in North India, expands in West India. Subsidiary opens apex lab in Mumbai - all nearby samples to be tested here instead of Delhi (positive).\n\n📍IL&FS pays 5 PSU Banks 87% of their ₹1,500 crore dues. Canara, Union Bank, Central, Punjab & Sind Bank.\n\n📍EV scooter co. Ather Energy to achieve $1 billion in revenue in 2023 and turn profitable in few years. Beneficiary: Hero Motors\n\n📍Outstanding dues by power distributors (discoms) to producers (gencos) has nearly halved to Rs 62,681 cr in last 1 year. Benficiary: PFC, REC (but priced in).\n\n📍Solar panel manufacters boost production as costs fall for polysilicon and wafer (RM for solar cells). Positive for Sterling Wilson Renewables, Borosil Renewables.\n\n1/2",
"visibility": "public_visible",
"created_at": "2023-01-09T09:05:41.226+05:30",
"updated_at": "2023-01-09T09:05:41.277+05:30",
"meta_info": null,
"type": "Post",
"likes_count": 4,
"replies_count": 1,
"widgets_data": [],
"is_liked": false,
"is_bookmarked": false,
"is_deleted": false,
"share_count": null
},
"relationships": {
"user": {
"data": {
"id": "83681",
"type": "user"
}
},
"poll": {
"data": null
},
"tagged_users": {
"data": []
},
"tagged_companies": {
"data": []
},
"tagged_topics": {
"data": []
},
"tagged_instruments": {
"data": []
},
"stories": {
"data": []
},
"attachments": {
"data": []
},
"media_files": {
"data": []
}
}
}, ],
"included": [
{
"id": "86806",
"type": "user",
"attributes": {
"display_name": "Ravi Gupta",
"username": "ravigupta132",
"dp_thumbnail": "/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBZ2E4IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--763b2ccc4372862bb99de6620aa9fe9661693603/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2RkhKbGMybDZaVjkwYjE5c2FXMXBkRnNIYVFHQWFRR0EiLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--7b4a499a9ad66d05dd5deec71f9adfd36a29feb5/image_cropper_1672468958147.jpg",
"market_view": "neutral"
},
"relationships": {}
},
{
"id": "483",
"type": "poll",
"attributes": {
"question": "#:user/3 \n#:user/77 \n#:user/52 #:user/3921 #:user/267 #:user/5 \n\nWhich sector will outperform in 2023 ???",
"options": [
"Metal",
"PSU banks",
"IT",
"Infrastructure"
],
"vote_count": 52,
"grouped_count": null,
"ends_at": "2023-01-14T14:38:01.760+05:30",
"my_vote": null,
"is_expired": false
},
"relationships": {
"base_post": {
"data": {
"id": "99412",
"type": "post"
}
}
}
}, ]
}
Basically, 2 lists in an object.
I am unable to deserialize this data. How do I de-serialize this data to get 2 lists? I need to get data based on a common id.
This is what I have currently (WIP):
Future<List<Datum>> getPostData() async {
try {
Response response = await _dio.get(url);
var jsonData = response.data;
//Map<String, dynamic> postMap = response.data;
final someMappedObjectList = <Datum>[];
for (var map in jsonData["data"]) {
Datum someObject = Datum(
id: map["id"],
type: map["type"],
attributes: map["attributes"],
relationships: map["relationships"]);
//final someObject = Datum.fromJson(map);
someMappedObjectList.add(someObject);
}
//List<dynamic> listDatum = postMap["data"].toList();
//List<Datum> listDatum = Datum.fromJson(jsonData["data"]) as List<Datum>;
return someMappedObjectList;
} //
catch (e) {
print(e);
return [];
}
}
Future<List> getPostIncluded() async {
try {
Response response = await _dio.get(url);
var jsonData = response.data;
Map<String, dynamic> postMap = response.data;
List<dynamic> listIncluded = postMap["included"].toList();
return listIncluded;
} //
catch (e) {
print(e);
return [];
}
}
Especially for something this complex, I strongly suggest using the json_serializable package. It will save you quite a bit of trouble in the long term.

DataWeave JSON Transformation/ Extract and concatenate values

I'm looking to go from a JSON structure that looks something like this:
{
"id": "955559665",
"timestamp": "2022-04-21 00:00:19",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15",
"remote_addr": "123.456.789.012",
"read": "0",
"data": {
"80928111": {
"field": "80928111",
"value": "Z01234567",
"flat_value": "Z01234567",
"label": "ID",
"type": "text"
},
"90924321": {
"field": "90924321",
"value": {
"first": "Jane",
"last": "Doe"
},
"flat_value": "first = Jane\nlast = Doe",
"label": "Name",
"type": "name"
},
"88888770": {
"field": "88888770",
"value": "jdoe001#gmail.com",
"flat_value": "jdoe001#gmail.com",
"label": "Email",
"type": "email"
},
"12345678": {
"field": "12345678",
"value": "https://www.google.com/subdomain/attachment/file.txt",
"flat_value": "https://www.google.com/subdomain/attachment/file.txt",
"label": "Choose File",
"type": "file"
}
}
}
Ultimately to something like this:
{
"name_val":"Name: first = Jane\nlast = Doe\nEmail: jdoe001#gmail.com\n",
"file": {
"id": "12345678C",
"name": "file.txt"
}
}
In the original JSON, the 'data' object represents a form submission. Each sub object represents a field on the submitted form. The only distinction I'm interested in is the 'type' of field identified as 'file'.
Every response that is not of 'file' type, I want to concatenate into one large String value that looks like: 'label1: flat_value1\nlabel2: flat_value2...'
Note, the number of actual fields is variable.
Then I need a second object to show the field of type 'file', by identifying the 'field' id, and the name of the file.
I've gotten pieces of this to work. For example, using pluck and filter, I've been able to separate the types of fields.
Something like this:
%dw 2.0
output application/json
---
[
"fields": payload.data pluck(
{
"field": $."label",
"value": $."flat_value",
"type": $."type"
}
) filter ($."type" != "file") default "",
"files": payload.data pluck(
{
"type": $."type",
"fieldId": $."field"
}
) filter ($."type" == "file") default ""
]
Gives me:
[
{
"fields": [
{
"field": "ID",
"value": "Z01234567",
"type": "text"
},
{
"field": "Name",
"value": "first = Jane\nlast = Doe",
"type": "name"
},
{
"field": "Email",
"value": "jdoe001#gmail.com",
"type": "email"
}
]
},
{
"files": [
{
"type": "file",
"fieldId": "12345678"
}
]
}
]
And playing around with a modified JSON input, I was able to easily see concatenation similar to how I want to see it, but not quite there:
%dw 2.0
output application/json
var inputJson = [
{
"field": "ID",
"value": "Z01234567",
"type": "text"
},
{
"field": "Name",
"value": "first = Jane\nlast = Doe",
"type": "name"
}
]
---
inputJson map ((value, index) -> value.field ++ ': ' ++ value.value)
Gives me:
[
"ID: Z01234567",
"Name: first = Jane\nlast = Doe"
]
But I can't seem to put it all together and go from Beginning to End.
There are several ways to implement this. I recommend to try to encapsulate the parts that you get working and use them as building blocks.
%dw 2.0
output application/json
fun fields(x) = x.data pluck(
{
"field": $."label",
"value": $."flat_value",
"type": $."type"
}
) filter ($."type" != "file") default ""
fun files(x) = x.data pluck(
{
"type": $."type",
"fieldId": $."field"
}
) filter ($."type" == "file") default ""
---
{
name_val: fields(payload) reduce ((item,acc="") -> acc ++ item.field ++ ': ' ++ item.value ++ "\n"),
files: files(payload)[0]
}
Output:
{
"name_val": "ID: Z01234567\nName: first = Jane\nlast = Doe\nEmail: jdoe001#gmail.com\n",
"files": {
"type": "file",
"fieldId": "12345678"
}
}

Get Object from http get API result

I know question has already asked but I can't do it with my get result.
My service get data from API return this kind of result :
{
"nhits": 581,
"parameters": {
"dataset": "communes-belges-2019",
"rows": 1,
"start": 0,
"facet": [
"niscode",
"region",
"nis_code_region"
],
"format": "json",
"timezone": "UTC"
},
"records": [
{
"datasetid": "communes-belges-2019",
"recordid": "65d40b7bc42f766b4fdb04c4a985766dc8b51717",
"fields": {
"shape_area": 79397718.576,
"mun_name_upper_fr": "ÉTALLE",
"arr_name_fr": "Virton",
"region": "Région wallonne",
"niscode": "85009",
"mun_off_language": "FR",
"geo_shape": {
"coordinates": [
[
[
5.678490965,
49.687217222
],
[
5.678462422,
49.6873304
]
]
],
"type": "Polygon"
},
"prov_name_fr": "Luxembourg",
"namefre": "Étalle",
"nom_commune": "Étalle",
"nis_code_region": "03000",
"mun_name_fr": "Étalle",
"reg_name_fr": "Région wallonne",
"mun_area_code": "BEL",
"modifdate": "2007-01-05",
"mun_type": "Commune/Gemeente/Gemeinde",
"mun_name_lower_fr": "étalle",
"prov_code": "80000",
"year": "2021-01-01",
"geo_point_2d": [
49.6639160352,
5.60896600843
]
},
"geometry": {
"type": "Point",
"coordinates": [
5.60896600843,
49.6639160352
]
},
"record_timestamp": "2019-05-24T09:44:14.333000+00:00"
}
],
"facet_groups": [
{
"name": "niscode",
"facets": [
{
"name": "11001",
"count": 1,
"state": "displayed",
"path": "11001"
},
{
"name": "33021",
"count": 1,
"state": "displayed",
"path": "33021"
},
{
"name": "33029",
"count": 1,
"state": "displayed",
"path": "33029"
},
{
"name": "33037",
"count": 1,
"state": "displayed",
"path": "33037"
}
]
},
{
"name": "region",
"facets": [
{
"name": "Région flamande",
"count": 299,
"state": "displayed",
"path": "Région flamande"
},
{
"name": "Région wallonne",
"count": 262,
"state": "displayed",
"path": "Région wallonne"
},
{
"name": "Région de Bruxelles-Capitale",
"count": 19,
"state": "displayed",
"path": "Région de Bruxelles-Capitale"
}
]
},
{
"name": "nis_code_region",
"facets": [
{
"name": "02000",
"count": 299,
"state": "displayed",
"path": "02000"
},
{
"name": "03000",
"count": 262,
"state": "displayed",
"path": "03000"
},
{
"name": "01000",
"count": 19,
"state": "displayed",
"path": "01000"
}
]
}
]
}
For the moment my service get data like this :
getProvinces(): Observable<any[]>{
return this._http.get<any>(this.apiProvinces)
.pipe(
map((response: any) => response.records as any[]),
catchError(this.handleError)
)
}
It returns a Observable<any[]> but I would like to get an object.
Therefore I defined a class with the below properties.
export class Record{
region : string;
nom_commune : string;
prov_name_fr: string;
records?: [];
constructor(reg: string, commune: string, prov: string) {
this.region = reg;
this.nom_commune = commune;
this.prov_name_fr = prov;
}
}
To reach that I try by replacing any[] by Record[] like below but it doesn't work.
getProvinces(): Observable<Record[]>{
return this._http.get<Record[]>(this.apiProvinces)
.pipe(
map(response => response as Record[]),
catchError(this.handleError)
)
}
In my component I defined an Observable<Record[]> like this :
public results$!: Observable<Record[]>;
And call the service like this :
this.results$ = this.apiService.getProvinces();
And try to see content in the html part:
<div *ngFor="let p of (results$ | async)">
{{p | json}}
</div>
I have the following error message : "Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays."
And can't access to my object.
Any suggestions is helpfull because. I'm absolutely new to ANgular.
Thanks
When you create an observable, you don't actually make a request to your api. To make the request and get the data from the observable, you need to subscribe.
this.apiService.getProvinces().subscribe((res) => (this.results = res));
This will execute the get request and return your data to the callback function. Get requests are finite observables, so you don't need to unsubscribe, but you will need to subscribe again if you want to make another request.

Efficient way of converting terraform JSON schema to AWS API JSON schema

I'm super new to Go and I'm trying to convert a JSON schema which contains terraform specific attribute names to AWS API specific JSON schema in Go.
For example:
The input is of type:
{
"egress": [
{
"cidr_blocks": [
"0.0.0.0/0"
],
"description": "Port 443",
"from_port": 443,
"protocol": "tcp",
"to_port": 443
}
],
"ingress": [
{
"cidr_blocks": [
"0.0.0.0/0"
],
"description": "Port 443",
"from_port": 443,
"protocol": "tcp",
"to_port": 443
}
],
"name": "my_sec_group",
"vpc_id": "${aws_vpc.my_vpc.id}"
}
And the desired output should be matching the AWS API object which would be something like this:
{
"SecurityGroups": [
{
"GroupName": "my_sec_group",
"IpPermissions": [
{
"FromPort": 443,
"IpProtocol": "tcp",
"IpRanges": [
{
"CidrIp": "0.0.0.0/0",
"Description": "Port 443"
}
],
"ToPort": 443
}
],
"IpPermissionsEgress": [
{
"FromPort": 443,
"IpProtocol": "tcp",
"IpRanges": [
{
"CidrIp": "0.0.0.0/0",
"Description": "Port 443"
}
],
"ToPort": 443
}
],
"VpcId": "${aws_vpc.my_vpc.id}"
}
]
}
What I have tried till now:
I. Create a generic JSON spec and use it to convert the input JSON to desired output
My generic JSON spec looks something like this:
{
"aws_security_group": [
{
"terraform_attribute": "name",
"attribute_type": "string",
"aws_attribute": "GroupName"
},
{
"terraform_attribute": "vpc_id",
"attribute_type": "string",
"aws_attribute": "VpcId"
},
{
"terraform_attribute": "description",
"attribute_type": "string",
"aws_attribute": "Description"
},
{
"terraform_attribute": "ingress",
"attribute_type": "list",
"aws_attribute": "IpPermissions",
"list_items": [
{
"terraform_attribute": "from_port",
"attribute_type": "string",
"aws_attribute": "FromPort"
},
{
"terraform_attribute": "to_port",
"attribute_type": "string",
"aws_attribute": "ToPort"
},
{
"terraform_attribute": "protocol",
"attribute_type": "string",
"aws_attribute": "IpProtocol"
},
{
"terraform_attribute": "cidr_blocks",
"attribute_type": "list",
"aws_attribute": "IpRanges",
"list_items": [
{
"terraform_attribute": "cidr_blocks.value",
"attribute_type": "string",
"aws_attribute": "CidrIp"
},
{
"terraform_attribute": "description",
"attribute_type": "string",
"aws_attribute": "Description"
}
]
}
]
},
{
"terraform_attribute": "egress",
"attribute_type": "list",
"aws_attribute": "IpPermissionsEgress",
"list_items": [
{
"terraform_attribute": "from_port",
"attribute_type": "string",
"aws_attribute": "FromPort"
},
{
"terraform_attribute": "to_port",
"attribute_type": "string",
"aws_attribute": "ToPort"
},
{
"terraform_attribute": "protocol",
"attribute_type": "string",
"aws_attribute": "IpProtocol"
},
{
"terraform_attribute": "cidr_blocks",
"attribute_type": "list",
"aws_attribute": "IpRanges",
"list_items": [
{
"terraform_attribute": "cidr_blocks.value",
"attribute_type": "string",
"aws_attribute": "CidrIp"
},
{
"terraform_attribute": "description",
"attribute_type": "string",
"aws_attribute": "Description"
}
]
}
]
}
]
}
Then I'm using this spec to convert the input JSON to desired output by recursively visiting each block within the input something like this:
for key, val := range configCopy {
for _, detail := range resourceDetails {
detail := detail.(map[string]interface{})
if detail["attribute_type"] == "string" && detail["terraform_attribute"] == key {
delete(configCopy, key)
configCopy[detail["aws_attribute"].(string)] = val
break
} else if detail["attribute_type"] == "list" && detail["terraform_attribute"] == key {
delete(configCopy, key)
configCopy[detail["aws_attribute"].(string)] =
buildListValue(val, detail["list_items"].([]interface{}))
}
}
}
func buildListValue(val interface{}, listItemVal []interface{}) []map[string]interface{} {
v := reflect.ValueOf(val)
var result []map[string]interface{}
fmt.Println("Val Kind:", v.Kind(), "Val String: ", v.String())
valCopy := make([]interface{}, v.Len())
if v.Kind() == reflect.Slice {
for i := 0; i < v.Len(); i++ {
valCopy[i] = v.Index(i).Interface()
}
}
for _, eachVal := range valCopy {
res := make(map[string]interface{})
f := reflect.ValueOf(eachVal)
if f.Kind() == reflect.Map {
for _, key := range f.MapKeys() {
fmt.Println("Key:", key.String(), " value:", f.MapIndex(key).Interface())
for _, listItem := range listItemVal {
listItem := listItem.(map[string]interface{})
if listItem["attribute_type"] == "string" && listItem["terraform_attribute"] == key.String() {
res[listItem["aws_attribute"].(string)] = f.MapIndex(key).Interface()
break
} else if listItem["attribute_type"] == "list" && listItem["terraform_attribute"] == key.String() {
res[listItem["aws_attribute"].(string)] =
buildListValue(f.MapIndex(key).Interface(), listItem["list_items"].([]interface{}))
break
}
}
}
} else if f.Kind() == reflect.String {
for _, listItem := range listItemVal {
listItem := listItem.(map[string]interface{})
if strings.HasSuffix(listItem["terraform_attribute"].(string), ".value") {
res[listItem["aws_attribute"].(string)] = f.String()
}
}
}
if len(res) > 0 {
result = append(result, res)
}
}
return result
}
Where configCopy is the input JSON. This approach works, but the problems associated with this approach are:
It is a time consuming process as I need to manually create a spec for each resource type.
Error prone as the transformation code is completely dependent on the manual spec being defined.
II. Tried using some open-source JSON transformers in Go like kazaam
Project kazaam takes a JSON string and a schema spec as input and produces the result using the given spec very similar to project Jolt.
I tried using a spec like this to convert the above input JSON:
[
{
"operation": "shift",
"spec": {
"SecurityGroups.Description": "description",
"SecurityGroups.GroupName": "name",
"SecurityGroups.VpcId": "vpc_id",
"SecurityGroups.IpPermissions.IpProtocol": "ingress[*].protocol",
"SecurityGroups.IpPermissions.FromPort": "ingress[*].from_port",
"SecurityGroups.IpPermissions.ToPort": "ingress[*].to_port",
"SecurityGroups.IpPermissions.IpRanges.CidrIp": "ingress[*].cidr_blocks[*]",
"SecurityGroups.IpPermissions.IpRanges.Description": "ingress[*].description",
"SecurityGroups.IpPermissionsEgress.IpProtocol": "egress[*].protocol",
"SecurityGroups.IpPermissionsEgress.FromPort": "egress[*].from_port",
"SecurityGroups.IpPermissionsEgress.ToPort": "egress[*].to_port",
"SecurityGroups.IpPermissionsEgress.IpRanges.CidrIp": "egress[*].cidr_blocks[*]",
"SecurityGroups.IpPermissionsEgress.IpRanges.Description": "egress[*].description"
}
}
]
But the result produced was something like the one shown below which is not what I was expecting:
{
"SecurityGroups": {
"IpPermissionsEgress": {
"FromPort": [
443
],
"ToPort": [
443
],
"IpRanges": {
"Description": [
"Port 443"
],
"CidrIp": [
[
"0.0.0.0/0"
]
]
},
"IpProtocol": [
"tcp"
]
},
"VpcId": "${aws_vpc.my_vpc.id}",
"IpPermissions": {
"FromPort": [
443
],
"ToPort": [
443
],
"IpRanges": {
"CidrIp": [
[
"0.0.0.0/0"
]
],
"Description": [
"Port 443"
]
},
"IpProtocol": [
"tcp"
]
},
"Description": null,
"GroupName": "my_sec_group"
}
}
Which is not as expected. I may be doing something wrong while constructing the kazaam spec, but I'm not really sure.
Is there any better/efficient way to address this use case? Any suggestions on this would be of great help.

How to check if a key exists in a nested JSON object in node?

I've got the following JSON being sent to the server from the browser:
{
"title": "Testing again 2",
"abstract": "An example document",
"_href": "http://google.com",
"tags": [ "person" ],
"attributes": [ {
"id": 1,
"type": "TEXT",
"data": "test"
} ],
"sections": [ {
"id": 1,
"type": "LIST",
"data": [ {
"revision": 124,
"text": "test"
} ]
} ]
}
I need to make sure that the keys "_href", "id" and "revision" are not in the object anyplace at any level.
I found this but it doesn't quite work.
I searched npms.io and found has-any-deep which you can use after JSON.parse ing the JSON.
you need to parse json then check into the data
var str = '{
"title": "Testing again 2",
"abstract": "An example document",
"_href": "http://google.com",
"tags": [ "person" ],
"attributes": [ {
"id": 1,
"type": "TEXT",
"data": "test"
} ],
"sections": [ {
"id": 1,
"type": "LIST",
"data": [ {
"revision": 124,
"text": "test"
} ]
} ]
}';
var jsonObj = JSON.parse(str);
if ( typeof jsonObj._href == 'undefined') {
// check
}
A simple but not 100% foolproof solution would be to parse the JSON to string, and just search for your keys:
var a = JSON.stringify(JSONObject);
var occurs = false;
['"_href"', '"id"', '"version"'].forEach(function(string) {
if(a.indexOf(string) > -1) occurs = true;
});
The issue of course, is if there are values that match
'_href', 'id', 'version' in your JSON. But if you want to use native JS, I guess this is a good bet.
var a = {
"title": "Testing again 2",
"abstract": "An example document",
"tags": [ "person" ],
"attributes": [ {
"type": "TEXT",
"data": "test"
} ],
"sections": [ {
"type": "_href asdad",
"data": [ {
"text": "test"
} ]
} ]
},
b = {
"title": "Testing again 2",
"abstract": "An example document",
"_href": "http://google.com",
"tags": [ "person" ],
"attributes": [ {
"id": 1,
"type": "TEXT",
"data": "test"
} ],
"sections": [ {
"id": 1,
"type": "LIST",
"data": [ {
"revision": 124,
"text": "test"
} ]
} ]
},
aJson = JSON.stringify(a),
bJson = JSON.stringify(b);
var occursa = false, occursb = false;
['"_href"', '"id"', '"version"'].forEach(function(string) {
if(aJson.indexOf(string) > -1) { occursa = true};
});
['"_href"', '"id"', '"version"'].forEach(function(string) {
if(bJson.indexOf(string) > -1) { occursb = true};
});
console.log("a");
console.log(occursa);
console.log("b");
console.log(occursb);
You could use the optional second reviver parameter to JSON.parse for this:
function hasBadProp(json) {
let badProp = false;
JSON.parse(json, (k, v) => {
if ([_href", "id", "revision"].includes(k)) badProp = true;
return v;
});
return badProp;
}