Faulty JSON is getting generated using boost::property_tree::ptree - c++

I am using boost::property_tree::ptree to add data and create JSON file. The following is the recursive code that I have written -
using Strings = vector<string>;
Strings _headers;
map<string, Strings> _subHeaders;
namespace pt = boost::property_tree;
void flushHeader(pt::ptree& headersNode, const Strings& headers) const {
for(auto& header : headers) {
pt::ptree headerNode;
pt::ptree subHeaderNode;
pt::ptree subHeaderObjNode;
headerNode.put(header, "");
if(_subHeaders.find(header) != _subHeaders.end()) {
flushHeader(subHeaderObjNode, _subHeaders.find(header)->second);
subHeaderNode.push_back(make_pair("", subHeaderObjNode));
headerNode.put_child(pt::ptree::path_type(header, '|'), subHeaderNode);
}
headersNode.push_back(make_pair("", headerNode));
}
}
void flushData(pt::ptree& parent) const {
pt::ptree headersNode;
flushHeader(headersNode, _headers);
parent.put_child("Headers", headersNode);
}
JSON file created using the above code is something like this -
"Headers": [
{
"A": ""
},
{
"B": [
[
{
"X": ""
},
{
"Y": ""
}
]
]
}
]
There are two brackets - [ after the value B whereas ideally only one [ should be present. So I want my JSON to look like this -
"Headers": [
{
"A": ""
},
{
"B": [
{
"X": ""
},
{
"Y": ""
}
]
}
]
I hope I have explained the problem clearly. What changes can I make in my code to get the desired JSON file?
Thanks.

Related

Read a sub json using boost property tree

I need to read a field in a JSON file which itself is a JSON. I need to read the JSON field in one go. Is there any way available? Sample JSON I am trying to read is provided below.
enter code here
{
"responses": [
{
"id": "1",
"status": 200,
"headers": {
"OData-Version": "4.0",
"Content-Type":"application/json;odata.metadata=minimal;odata.streaming=true"
},
"body": {
"createdDateTime": "2021-04-22T09:24:59.394Z",
"displayName": "Test1",
"visibility": "public",
"isMembershipLimitedToOwners": false,
"discoverySettings": { "showInTeamsSearchAndSuggestions": true },
"memberSettings": {
"allowCreateUpdateChannels": true,
"allowCreateUpdateRemoveConnectors": true
},
"guestSettings": {
"allowCreateUpdateChannels": true,
"allowDeleteChannels": false
},
"messagingSettings": {
"allowUserEditMessages": true,
"allowChannelMentions": true
},
"funSettings": {
"allowGiphy": true,
"allowCustomMemes": true
}
} } ]
}
I am trying to read the "body" field using the code below (json is read in boost::property_tree::ptree jsonBatchResponse jsonBatchResponse). But strBody is empty and it doesn't read the "Body" field correctly. :
enter code here
for (auto& v : jsonBatchResponse.get_child("responses"))
{
std::string strID = v.second.get<std::string>("id", "");
std::string strStatus = v.second.get<std::string>("status", "");
std::string strBody = v.second.get<std::string>("body", "");
}
It looks like v.second.getstd::string("body", "") is not the right way to read the JSON field. Is there any other way available (other than reading individual fields in the JSON value)? Please let me know.
The body is not a string.
So, getting the child object would be in order:
for (auto const& v : jsonBatchResponse.get_child("responses")) {
std::string strID = v.second.get<std::string>("id", "");
std::string strStatus = v.second.get<std::string>("status", "");
ptree const& body = v.second.get_child("body");
}
If you add some output to that loop with e.g.
std::cout << std::quoted(strID) << "\n";
std::cout << std::quoted(strStatus) << "\n";
write_json(std::cout, body);
It will print Live On Coliru
"1"
"200"
{
"createdDateTime": "2021-04-22T09:24:59.394Z",
"displayName": "Test1",
"visibility": "public",
"isMembershipLimitedToOwners": "false",
"discoverySettings": {
"showInTeamsSearchAndSuggestions": "true"
},
"memberSettings": {
"allowCreateUpdateChannels": "true",
"allowCreateUpdateRemoveConnectors": "true"
},
"guestSettings": {
"allowCreateUpdateChannels": "true",
"allowDeleteChannels": "false"
},
"messagingSettings": {
"allowUserEditMessages": "true",
"allowChannelMentions": "true"
},
"funSettings": {
"allowGiphy": "true",
"allowCustomMemes": "true"
}
}
BONUS: Using a proper JSON library instead
Boost Property Tree is NOT a JSON library, and therefore has a lot of limitations.
Instead I suggest using Boost JSON:
Live On Coliru
#include <boost/json.hpp>
#include <boost/json/src.hpp>
#include <iostream>
namespace json = boost::json;
extern std::string sample;
int main() {
json::object jsonBatchResponse = json::parse(sample).as_object();
for (auto& v : jsonBatchResponse["responses"].as_array()) {
auto& res = v.as_object();
json::value id = res["id"], // string
status = res["status"], // integer
body = res["body"]; // object
std::cout << id << "\n";
std::cout << status << "\n";
std::cout << body << "\n";
}
}
std::string sample = R"(
{
"responses": [{
"id": "1",
"status": 200,
"headers": {
"OData-Version": "4.0",
"Content-Type": "application/json;odata.metadata=minimal;odata.streaming=true"
},
"body": {
"createdDateTime": "2021-04-22T09:24:59.394Z",
"displayName": "Test1",
"visibility": "public",
"isMembershipLimitedToOwners": false,
"discoverySettings": {
"showInTeamsSearchAndSuggestions": true
},
"memberSettings": {
"allowCreateUpdateChannels": true,
"allowCreateUpdateRemoveConnectors": true
},
"guestSettings": {
"allowCreateUpdateChannels": true,
"allowDeleteChannels": false
},
"messagingSettings": {
"allowUserEditMessages": true,
"allowChannelMentions": true
},
"funSettings": {
"allowGiphy": true,
"allowCustomMemes": true
}
}
}]
}
)";
Prints
"1"
200
{"createdDateTime":"2021-04-22T09:24:59.394Z","displayName":"Test1","visibility":"public","isMembershipLimitedToOwners":false,"discoverySettings":{"showInTeamsSearchAndSuggestions":true},"memberSettings":{"allowCreateUpdateChannels":true,"allowCreateUpdateRemoveConnectors":true},"guestSettings":{"allowCreateUpdateChannels":true,"allowDeleteChannels":false},"messagingSettings":{"allowUserEditMessages":true,"allowChannelMentions":true},"funSettings":{"allowGiphy":true,"allowCustomMemes":true}}

Mongo query to find not null string inside list of objects

I want to query an array of objects with a particular key should have text in it.
this is the query I have tried
to find disclaimer.text exists and not empty. But It always prints 0
db.slideshows.count({"config.slides": { $elemMatch: {disclaimer: {"text" : {"$exists" : true, "$ne" : ""} }} } })
This is my data
{
"id": 1002,
"config": {
"firstSlide": "vehicle",
"slides": [
{
"slideKey": "sk1",
"disclaimer": {
"text": ""
}
},
{
"slideKey": "sk2"
}
]
}
}
{
"id": 1003,
"config": {
"firstSlide": "book",
"slides": [
{
"slideKey": "sk3",
"disclaimer": {
"text": "Hello"
}
},
{
"slideKey": "sk4"
}
]
}
}
{
"id": 1004,
"config": {
"firstSlide": "book",
"slides": [
{
"slideKey": "sk3",
"disclaimer": {
"text": "nope"
}
},
{
"slideKey": "sk4",
"disclaimer": {
"text": ""
}
}
]
}
}
I want all the rows which have not empty disclaimer.text. ex. in the above set I need to get id 1004 and 1003 as a result.
Try
db.slideshows.count({
"config.slides": {
$elemMatch: {
"disclaimer.text": {
"$exists": true,
"$ne": ""
}
}
}
})

How to merge same key json data into one in c++ using nlhoman json

I have below JSON data:
{
"Created": "2019-08-01T14:36:49Z",
"Tags": [
{
"ObjectId": "1",
"Time": 6,
"TrackerId": "W1"
},
{
"ObjectId": "2",
"Time": 4,
"TrackerId": "E34"
},
{
"ObjectId": "3",
"Time": 4,
"TrackerId": "W1"
},
{
"ObjectId": "4",
"Time": 8,
"TrackerId": "E34"
}
],
"id": 0
}
In the above JSON data, we can see that we have 4 object id's but only 2 tracker id. I need to merge the data which has the same TrackerId and also add their time. So above data will become:
{
"Created": "2019-08-01T14:36:49Z",
"Tags": [
{
"Time": 10,
"TrackerId": "W1"
},
{
"Time": 12,
"TrackerId": "E34"
}
],
"id": 0
}
I am using Nlohmann JSON library for C++. How can we achieve this?
You can build a map of the trackers and then feed them into the JSON object:
json merge_objects(const json& data)
{
std::map<std::string, int> times;
for (const auto& entry : data["Tags"]) {
times[entry["TrackerId"]] += static_cast<int>(entry["Time"]);
}
json result;
result["Created"] = data["Created"];
for (const auto& [id, time] : times) {
json tag;
tag["Time"] = time;
tag["TrackerId"] = id;
result["Tags"].push_back(tag);
}
return result;
}
(live demo)

How can I exclude results from elasticsearch based on the contents of a field?

I'm using elasticsearch on AWS to store logs from Cloudfront. I have created a simple query that will give me all entries from the past 24h, sorted from new to old:
{
"from": 0,
"size": 1000,
"query": {
"bool": {
"must": [
{ "match": { "site_name": "some-site" } }
],
"filter": [
{
"range": {
"timestamp": {
"lt": "now",
"gte": "now-1d"
}
}
}
]
}
},
"sort": [
{ "timestamp": { "order": "desc" } }
]
}
Now, there a are certain sources (based on the user agent) for which I would like to exclude results. So my question boils down to this:
How can I filter out entries from the results when a certain field contains a certain string? Or:
query.filter.where('cs_user_agent').does.not.contain('Some string')
(This is not real code, obviously.)
I have tried to make sense of the Elasticsearch documentation, but I couldn't find a good example of how to achieve this.
I hope this makes sense. Thanks in advance!
Okay, I figured it out. What I've done is use a Bool Query in combination with a wildcard:
{
"from": 0,
"size": 1000,
"query": {
"bool": {
"must": [
{ "match": { "site_name": "some-site" } }
],
"filter": [
{
"range": {
"timestamp": {
"lt": "now",
"gte": "now-1d"
}
}
}
],
"must_not": [
{ "wildcard": { "cs_user_agent": "some string*" } }
]
}
},
"sort": [
{ "timestamp": { "order": "desc" } }
]
}
This basically matches any user agent string containing "some string", and then filters it out (because of the "must_not").
I hope this helps others who run into this problem.
nod.js client version:
const { from, size, value, tagsIdExclude } = req.body;
const { body } = await elasticWrapper.client.search({
index: ElasticIndexs.Tags,
body: {
from: from,
size: size,
query: {
bool: {
must: {
wildcard: {
name: {
value: `*${value}*`,
boost: 1.0,
rewrite: 'constant_score',
},
},
},
filter: {
bool: {
must_not: [
{
terms: {
id: tagsIdExclude ? tagsIdExclude : [],
},
},
],
},
},
},
},
},
});

is it possible to write regular expression in $cond in MongoDB

I need to use $cond to combine differenet column, and one $cond I need to write is as following:
create_widget: {
$sum:{
$cond:[{$and: [ {$eq: ['$Method', 'POST']},
{Url:{$regex: /.*\/widgets$/}} ]}, 1, 0]
}
}
and this code is not right, it seems, regular expression can not be put here.Is there any other way to do this? I want to match Url and regular expression and put the code under $cond.
A sample data looks as
{"BrandId":"a","SessionId":"a1","Method":"POST","Url":"/sample/widgets"}
{"BrandId":"a","SessionId":"a2","Method":"POST","Url":"/sample/blog"}
{"BrandId":"b","SessionId":"b1","Method":"PUT","Url":"/sample/widgets"}
The whole code I wrote is as following:
db.tmpAll.aggregate([
{$group: {
_id: {BrandId:'$BrandId'},
SessionId: {$addToSet: '$SessionId'},
create_widget: {
$sum:{
$cond:[{$and: [ {$eq: ['$Method', 'POST']},
{} ]}, 1, 0]
}
}
}},
{$group: {
_id: '$_id.BrandId',
distinct_session: {$sum: {$size: '$SessionId'}},
create_widget: {$sum: '$create_widget'}
}}
]);
The expected result of sample code is
{ "_id" : "a", "distinct_session" : 2, "create_widget" : 1 }
{ "_id" : "b", "distinct_session" : 1, "create_widget" : 0 }
For MongoDB 4.2 and newer production releases, and in the 4.1.11 and newer development versions, use $regexMatch which is a syntactic sugar on top of $regexFind which can be used for regex matching and capturing.
db.tmpAll.aggregate([
{ "$group": {
"_id": {
"BrandId": "$BrandId",
"SessionId": "$SessionId"
},
"widget_count": {
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": ["$Method", "POST"] },
{ "$regexMatch": {
"input": "$Url",
"regex": /widget/
} }
]
}, 1, 0
]
}
},
"session_count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.BrandId",
"create_widget": { "$sum": "$widget_count" },
"distinct_session": { "$sum": "$session_count" }
} }
]);
There is an open JIRA issue for this SERVER-8892 - Use $regex as the expression in a $cond. However, as a workaround, For older MongoDB versions which do not have the above features, use the following workaround in your aggregation pipeline.
It uses the $substr operator in the $project operator stage to extract the part of the URL and acts as a workaround for the regex. :
db.tmpAll.aggregate([
{ "$group": {
"_id": {
"BrandId": "$BrandId",
"SessionId": "$SessionId"
},
"widget_count": {
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": ["$Method", "POST"] },
{ "$eq": [ { "$substr": [ "$Url", 8, -1 ] }, "widget"] }
]
}, 1, 0
]
}
},
"session_count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.BrandId",
"create_widget": { "$sum": "$widget_count" },
"distinct_session": { "$sum": "$session_count" }
} }
]);
Output
/* 1 */
{
"result" : [
{
"_id" : "a",
"create_widget" : 1,
"distinct_session" : 2
},
{
"_id" : "b",
"create_widget" : 0,
"distinct_session" : 1
}
],
"ok" : 1
}