Conditional Anonymous type - json

I am working on Web API and using Anonymous type to make JSON as output. I am stuck in the following scenario:
If there is no record(VALUE) available then i don't want to show that KEY. Meaning, Key should only appear when and only when there is value.
Below is the JSON object i am creating -
"TU": [
{
"BLOCK": [
[
"00:00",
"00:59"
]
]
}
],
"WE": [],// empty
"TH": [],// empty
"FR": [],// empty
"SA": [] // empty
Here for Tuesday we do have records and hence its showing but later for WE,TH,FR,SA there are not records and hence i don't want to show them so my result will be MO/TU only.
I am using below code:
var result = new
{
CustomerID = custId,
DeviceID = dId,
Kind = kind,
WebList = filter.Select(filt => new
{
URL = filt.FilterName,
TimeBlockFlag = new ChicoHelper().GetFlag(browserlimit, filt.ID, filt.FilterOptionID, KindId),
DAILY = browserlimit.Where(xx => xx.FilterID == filt.ID && xx.OptionTypeID == daily).Select(xx => xx.BlockTimeLimit).SingleOrDefault(),
WEEKLY = browserlimit.Where(xx => xx.FilterID == filt.ID && xx.OptionTypeID == weekly).Select(xx => xx.BlockTimeLimit).SingleOrDefault(),
MONTHLY = browserlimit.Where(xx => xx.FilterID == filt.ID && xx.OptionTypeID == monthly).Select(xx => xx.BlockTimeLimit).SingleOrDefault(),
HASVALUES = browserlimit.Where(xx => xx.FilterID == filt.ID).Count() > 0 ? 1 : 0,
BLOCKTYPE = new ChicoHelper().GetBlockType(browserlimit,filt.ID,filt.FilterOptionID,KindId),
SU = blockedlimit.Where(x => x.OptionID == sunday && x.FilterID == filt.ID).GroupBy(x => new { x.BlockDay })
.Select(x => new
{
BLOCK = x.Select(y =>
new[] { y.BlockStartTime.MakeFormatedTime(), y.BlockEndTime.MakeFormatedTime() }
)
}),
MO = blockedlimit.Where(x => x.OptionID == monday && x.FilterID == filt.ID).GroupBy(x => new { x.BlockDay })
.Select(x => new
{
BLOCK = x.Select(y =>
new[] { y.BlockStartTime.MakeFormatedTime(), y.BlockEndTime.MakeFormatedTime() }
)
}),
TU = blockedlimit.Where(x => x.OptionID == tuesday && x.FilterID == filt.ID).GroupBy(x => new { x.BlockDay })
.Select(x => new
{
BLOCK = x.Select(y =>
new[] { y.BlockStartTime.MakeFormatedTime(), y.BlockEndTime.MakeFormatedTime() }
)
}),
// if i can put some condition like if there is not record for WE then don't show it.
WE = blockedlimit.Where(x => x.OptionID == wednesday && x.FilterID == filt.ID).GroupBy(x => new { x.BlockDay })
.Select(x => new
{
BLOCK = x.Select(y =>
new[] { y.BlockStartTime.MakeFormatedTime(), y.BlockEndTime.MakeFormatedTime() }
)
}),
The main reason for doing this is to reduce the JSON size which will be consumed by Mobile Devices.
Please help me with this.

The properties of an anonymous type are fixed at compile-time - you can't make them conditional. However, some other approaches you might want to think about:
You could investigate whether a property is still included in the JSON representation if its value is null. If it's not, you could add an extension method NullIfEmpty() which returns null if its input is empty.
You could try performing the JSON conversion from the anonymous type in code first, then delete any properties with an empty set of results, then just return that JSON object from the API. (I don't know Web API myself, but there must be a way of saying "Here's a JSON object - ask it for its string representation" rather than using an anonymous type.)
You could ditch the anonymous type entirely, and build up the JSON representation programmatically, setting just the properties you want.
In any approach, I would strongly advise you to extract a common method to come up with the property value based on a day of the week, so you can have:
...
SU = blockedLimit.GetDayBlocks(sunday),
MO = blockedLimit.GetDayBlocks(monday),
TU = blockedLimit.GetDayBlocks(tuesday),
...
There's no reason to have all that code repeated 7 times. In fact, I'd probably refactor that part before doing anything else - it'll make it easier to experiment.

Related

How to extract JSON value in strangely formatted array?

I'm using a custom script for importing JSON into Google Sheets through a function. I can import values from propertys without any problem, but I have some problem with a specific array. It is a property which contains more information, but it seems the formatting makes the array into one single value instead of several (something with the slashes?). First, the script:
function getStat(url, propertyName)
{
let content = UrlFetchApp.fetch(url).getContentText();
let parsed = JSON.parse(content);
let processed = parsed.data
.filter(e =>
// Conditions go here:
e.season_format === 'Domestic League' &&
e.season === '2020/2021'
)
.map(e => e.stats[propertyName]);
return processed;
}
I want to get the value after "3" in the array called additional_info (simplified version below). But when I try to get the value, instead I get the third character in the array. I don't get "55" which is the value. I've tried with a bunch of variants. But I can't get it to work. For example, additional_info["3"] returns the third character in the array, not the value. Any tips? I've no problem getting the values of suspended_matches and home_AttackAdvantage.
{
"success": true,
"data": [
{
"season": "2020/2021",
"season_format": "Domestic League",
"stats": {
"suspended_matches": 20,
"homeAttackAdvantage": 3,
"additional_info": "{\"1\":1,\"2\":2,\"3\":55,\"4\"}"
}
}
]
}
The issue was that additional_info is yet another JSON string, so you have to parse it again.
function getStat(url, propertyName, additionalProp)
{
let content = UrlFetchApp.fetch(url).getContentText();
let parsed = JSON.parse(content);
let processed = parsed.data
.filter(e =>
// Conditions go here:
e.season_format === 'Domestic League' &&
e.season === '2020/2021'
)
.map(e => additionalProp
? [
e.stats[propertyName],
JSON.parse(e.stats.additional_info)[additionalProp]
]
: e.stats[propertyName]
);
return processed;
}
This gives you a function you can use in a formula:
=getStat(
"https://api.footystats.org/team?key=example&team_id=93",
"suspended_matches",
"330"
)
If you don't specify the third argument, it will just return a single column.

Search items in external JSON

I have the URL of a JSON file and I want to get all the items with the same value.
Example:
http://sampleurl.com has this JSON
`{
"posts":[
{
"authors":[
{
{"name":"John",
"age": 30
},
{"name":"John",
"age": 35
}
}
]
}
]
}`
What I want to do is to list all those authors with the same name together with their age.
I have tried this with no success:
`var allposts = "http://sampleurl.com";
$.each(allposts.posts.authors, function(i, v) {
if (v.name == "John") {
alert("Ok");
return;
}
});`
Thanks
You need to get the data via an Ajax call - $.getJSON:
const authors = {};
$.getJSON( "http://sampleurl.com", data =>
data.posts.authors.forEach(author => {
authors[author.name] = authors[author.name] || []
authors[author.name].push(author)
});
);
At the end you have an object keyed on unique author names, with each key containing as its value an array of the authors with that name. You can do further processing to transform that to the data structure you need.
This example doesn't deal with data coming back that isn't in the shape you expect. For example, if some author records are missing a name, you will end up with a key undefined. And if there is no authors key or no posts key in the returned object you will get an exception.
So you have to decide how your program should behave in those cases. Should it explode? Or return an empty object? If you want it to continue with an empty object:
const authors = {};
$.getJSON( "http://sampleurl.com", data =>
if (data.posts && data.posts.authors) {
authors.forEach(author => {
const name = author.name || 'unknown';
authors[author.name] = authors[author.name] || []
authors[author.name].push(author)
});
} else {
console.log('Warning! Data from API did not contain posts.authors!')
}
);
Note that neither of these examples deal with the AJAX call itself failing. For that you need to chain a .fail() handler:
const authors = {};
const url = "http://sampleurl.com"
$.getJSON( url, data =>
if (data.posts && data.posts.authors) {
authors.forEach(author => {
const name = author.name || 'unknown';
authors[author.name] = authors[author.name] || []
authors[author.name].push(author)
});
} else {
console.log('Warning! Data from API did not contain posts.authors!')
}
).fail(res => console.log(`Ajax call to ${url} failed with message ${res.responseText}!`);
10% of programming is getting it to work. The other 90% is coding for what happens when it doesn't work.

I'm reading in a JSON of nested JavaScript Objects - I want to 'sort' it so objects that have a field with a certain value go to the top

I'm reading in a JSON that has a map of javascript objects. So.. for example :
{
offers : {
"1":{"id":"1", "category":"a", "offerType":"LS"},
"2":{"id":"2", "category":"a", "offerType":"EX"},
"3":{"id":"3", "category":"a", "offerType":"EX"},
"4":{"id":"4", "category":"a", "offerType":"LS"}
}
}
When I read this JSON, I am storing it in local storage. I want to "sort" is so that all offers that have offerType of "LS" show up on the TOP of my object in local storage.
The reason I want to do this is so when I display these offers on my site, the ones with offerType "LS" will display first.
I am doing this is Angular :
let offers = data.offers;
if (offers != null) {
for (var index in offers) {
var offer = offers[index];
if ( offer != undefined) {
if (offer.offerType == 'LS'){
offersLS = [...offersLS, offer];
}
}
}
if (offersLS != null){
offersLS.forEach(offerLS => {
let key = offerLS['id'];
listOffers = offers[key], listOffers;
});
}
listOffers = listOffers, offers;
}
listOffers is what ends up getting saved as my local storage object. I have tried to do it like : listOffers = [...offersLS, ...offers] but that obviously saves it in my localStorage as an array and I need it to be a 'map' of these objects or object of objects..not exactly sure what the correct terminology would be.
First, you have to know that sort an object may be a bad idea, it's not designed for that. Think about use arrays.
Anyway the solution is quite simple, it's about sort the keys then redo an object based on this new keys order:
const offers = {
"1":{"id":"1", "category":"a", "offerType":"LS"},
"2":{"id":"2", "category":"a", "offerType":"EX"},
"3":{"id":"3", "category":"a", "offerType":"EX"},
"4":{"id":"4", "category":"a", "offerType":"LS"}
};
let value1;
const sortedOffers = Object.keys(offers)
.sort((k1, k2) => {
// your sort rules here
value1 = offers[k1];
return value1.offerType === 'LS' ? -1 : 1;
})
.reduce((o, k) => {
o[k] = offers[k];
return o;
}, {});

operator '.' cannot be applied to lambda expression

I'm trying to create linq lambda expression to return customer whose first or last name starts with specific letters. However i get the error on .select saying that:
operator '.' cannot be applied to lambda expression.
public JsonResult GetCust(string term)
{
var data = context.Customers
.Where((dr => dr.First.StartsWith(term) == true) || (dr => dr.Last.StartsWith(term) == true))
.Select(dr => new { Name=String.Concat(dr.First, dr.Last), Adrs = dr.Street, value = dr.CustID })
.Take(10);
return Json(data, JsonRequestBehavior.AllowGet);
}
Any idea how can I return needed data?
In the following line:
.Where((dr => dr.First.StartsWith(term) == true) || (dr => dr.Last.StartsWith(term) == true))
you are using the ||-Operator on two lambda-expressions.
The Where-Clause should more look like this:
.Where(dr => dr.First.StartsWith(term) || dr.Last.StartsWith(term))

Linq - pulling a value from a null query result

I have a linq query that needs to pull a date column out of a row. The expression currently looks like this
myObject.OrderByDescending(s=> s.MyDate).Where(s => s.CRAStatus.Description == "CheckedOut").FirstOrDefault().MyDate)
The problem is that if there are no rows that are "CheckedOut", the query will return a null and attempting to get "MyDate" will throw an exception. We have some verbose solutions, like:
.ForMember(dest => dest.CheckOutDate, opt => opt.MapFrom(src => {
var temp = src.CRAStatusChangeEvents.OrderByDescending(s=> s.MyDate).Where(s => s.CRAStatus.Description == "CheckedOut").FirstOrDefault();
return temp == null ? temp.MyDate : null;
}));
But it would be nice to find something a little more concise. Any Ideas?
Why not
myObject.OrderByDescending(s=> s.MyDate)
.Where(s => s.CRAStatus.Description == "CheckedOut")
.Select(s => s.MyDate as DateTime?)
.FirstOrDefault();
or
myObject.Where(s => s.CRAStatus.Description == "CheckedOut")
.Max(s => s.MyDate as DateTime?);
One option is to set the default if empty to an "empty" instance (think of string.Empty--its a known instance that represents an empty result):
var date = (myObject
.OrderByDescending(s=> s.MyDate)
.Where(s => s.CRAStatus.Description == "CheckedOut")
.DefaultIfEmpty(MyObject.Empty)
.FirstOrDefault()).MyDate;
Here's a snippet that shows how it works:
var strings = new string[]{"one", "two"};
var length =
(strings.Where(s=>s.Length > 5)
.DefaultIfEmpty(string.Empty)
.FirstOrDefault()).Length;
run that and length is 0. Remove the DefaultIfEmpty line and you get a NRE.
var checkedOut = myObject.Where(s => s.CRAStatus.Description == "CheckedOut");
if (checkedOut.Count() > 0) {
var result = checkedOut.Max(s=> s.MyDate).MyDate;
}
How about an extension method?
static class MyObjectEnumerableExtensions
{
public static TMember GetMemberOfFirstOrDefault<TMember>(this IEnumerable<MyObject> items, Func<MyObject, TMember> getMember)
{
MyObject first = items.FirstOrDefault();
if (first != null)
{
return getMember(first);
}
else
{
return default(TMember);
}
}
}
Sample usage:
List<MyObject> objects = new List<MyObject>();
objects.Add(new MyObject { MyDate = DateTime.MinValue });
var filteredObjects = from s in objects where s.MyDate > DateTime.MinValue select s;
DateTime date = filteredObjects.GetMemberOfFirstOrDefault(s => s.MyDate);
Console.WriteLine(date);