Parsing JSON into an object with a defined 'schema' - javascript

I have a user specified JSON object that I'm attempting to process in the browser.
The problem is that it needs to match an existing object.
They can't accidentally:
forget to include some fields.
typo fields or deliberately add new fields.
Is there a way to handle this?
so basically if I have an object with foo and bar members, I want their defaults if the user's json is just {} ... and if they accidentally send something like {bart: "asdf";} (typo on 'bar') then I want it to generate an exception.

var default_object = { ... };
var allowed_keys = [ "key1", "key2", ... ];
var new_object = default_object.clone();
for (var key in json_object) {
if (json_object.hasOwnProperty(key)) {
if (allowed_keys.indexOf(key) == -1) {
// Report error here
} else {
new_object[key] = json_object[key];
}
}
}
See here for how to write the clone method I used above. If you use jQuery, you could simplify some of this code by using $.extend().

Related

How to replace the object key name dynamically Angular 8

I want to rename the factORLossTree into savefactORLossTree dynamically inside the function from below payload.
I am getting below data on payload after submitting the form.
{
"cluster":"Europe",
"factory":"Caivano",
"factoryId":"Caivano",
"factORLossTree":[
{
"skuid":"000000000067334539",
"skuDescription":"MAG 55ml Mini PistHazelnut 8MP x6x120 EB",
"levelLosses":[
{
"level1":"Line Scheduling Losses",
"variancePer":100
}
],
"isRowChecked":false
}
],
"submitType":"po"
}
Below is my code .
saveOrUpdateORData() {
const formData = Object.assign({}, this.orLosstreeForm.value);
if (formData.factORLossTree.length === 0) {
this.dialogService.openDialog('Data Not Available');
return false;
}
console.log(formData,"formdata");
return;
}
Expected Output
{
"cluster":"Europe",
"factory":"Caivano",
"factoryId":"Caivano",
"savefactORLossTree":[
{
"skuid":"000000000067334539",
"skuDescription":"MAG 55ml Mini PistHazelnut 8MP x6x120 EB",
"levelLosses":[
{
"level1":"Line Scheduling Losses",
"variancePer":100
}
],
"isRowChecked":false
}
],
"submitType":"po"
}
Can anyone please help me to do this.
Sure thing! Worth mentioning that this issue is purely JS and has nothing to do with your framework (e.g. - Angular)
To the point: let's assume that your payload is stored in a variable called payload
First - check if the property exists. Then - replace it:
const propertyName = 'put the property name here'
const newPropertyName = 'put the new property name here'
if (payload.hasOwnProperty(propertyName)) {
payload[newPropertyName] = payload[propertyName]
delete payload[propertyName]
}
Why this is working? Because we are creating another reference to the original data before deleting the property. At the end we end-up with one reference having the updated name
If (from some reason) you need to clone the data, follow this pattern instead:
if (payload.hasOwnProperty(propertyName)) {
const clone = JSON.parse(JSON.stringify(payload[propertyName]))
payload[newPropertyName] = clone
delete payload[propertyName]
}
I assume that your property always contains an object. If the type of the property is primitive (e.g. - number, string or boolean) you can just skip the cloning phase all together (primitives are copied by their values while only objects are handled by their reference)

Issue with pulling JSON data in SuiteScript - NetSuite

I am trying to see if a value in the "apply" sublist for customer payment data has changed and do some action based on it.
My SuiteScript is as follows:
define(['N/record', 'N/https'],
function(record,https)
{
function afterSubmit(context)
{
var oldRec = context.oldRecord;
log.debug({title: 'oldRec ', details: oldRec });
// This log shows that the JSON has an
// attribute called sublists which contains "apply" which has all the applied payments
// eg: {"id":"1234", "type":"customerpayment", "fields":{all the fields},
// "sublists": {"apply" : {"line 1"...}}}
var oldRecSublists = oldRec.sublists;
log.debug({title: 'oldRecApply ', details: oldRecSublists });
// This returns empty or null though there is data
What am I doing wrong here?
Basically what I am trying to achieve is compare the context.oldRecord.sublists.apply and context.newRecord.sublists.apply to find if the amt has changed or not.
Is there a better way to do this is SuiteScript 2.0?
Thanks in advance!
Part of what is going on there is that it looks like you are trying to spelunk the NS data structure by what you see in the print statement. You are not using the NS api at all.
When you send the NS object to the log function I believe it goes through a custom JSON.stringify process so if you want to just inspect values you can do:
var oldRecObj = JSON.parse(JSON.stringify(oldRec));
now oldRecObj can be inspected as though it were a simple object. But you won't be able to manipulate it at all.
You should be using the NS schema browser
and referring to the help docs for operations on N/record
A snippet I often use for dealing with sublists is:
function iter(rec, listName, cb){
var lim = rec.getLineCount({sublistId:listName});
var i = 0;
var getV = function (fld){
return rec.getSublistValue({sublistId:listName, fieldId:fld, line:i});
};
var setV = function(fld, val){
rec.setSublistValue({sublistId:listName, fieldId:fld, line:i, value:val});
};
for(; i< lim; i++){
cb(i, getV, setV);
}
}
and then
iter(oldRec, 'apply', function(idx, getV, setV){
var oldApplied = getV('applied');
});

Store function with ID as a key in JavaScript?

I have lots of packets that come through to my javascript via web sockets, each of these packets have a packet id, I want to create some sort of array or object that assigns each packet id a function to call, which would handle that packet.
I also want to include the 'packetData' parameter to all functions that are called, so it can get the other parts of data inside the function in order to handle the packet appropriately.
Heres what I have so far...
I listen for a message on WebSockets...
socket.onmessage = function (e) {
onPacket(JSON.parse(e.data));
};
Then I pass it to this function, I send my data in JSON format so I parse it to a key value object, then pass it to this function...
function onPacket(packetData) {
const packetId = packetData['packet_id'];
// TODO: Call the function...
}
The part I am stuck with is how do I create the array or keyvalue object to call the function? I store all my functions in a seperate file called "packet_events.js"
I did have a go at doing it myself (the object key value) but I was told it isn't valid JS, I'm stuck and sort of need some help, can anyone help?
My attempt:
var incomingHeaders = {
1: packetOne(),
};
If I still haven't made it clear, I want something like this, but with an array to save me constantly writing if statements..
function onPacket(packetData) {
const packetId = packetData['packet_id'];
if (packetId == 1) {
packetOne(packetData);
}
if (packetId == 2) {
packetTwo(packetData);
}
if (packetId == 3) {
packetThree(packetData);
}
}
Use object instead of arrays and create maping of ids into functions e.g.
var idToFunction = {
0: packetOne,
1: packetTwo,
2: packetThree
}
then inside onPacket your retrievied function based on id and executes it
function onPacket(packetData) {
const packetId = packetData['packet_id'];
var processPacket = idToFunction[packetId];
if (processPacket) {
processPacket(packetData);
}
else {
throw new Error("Invalid packed id: "+packetId);
}
}
Step 1: Create a function object that holds mapping from your packedId to your function.
Step 2: Iterate over the packets array to call the respective function.
Follow the snippet:
function packetOne() {
console.log('one')
}
function packetTwo() {
console.log('two')
}
let functionObject = {
1: packetOne,
2: packetTwo,
}
let packets = [
{ packetId: 1},
{ packetId: 2}
]
packets.forEach(function(packet){
functionObject[packet.packetId]()
})

Breeze Partial initializer

I have a Single Page Application that is working pretty well so far but I have run into an issue I am unable to figure out. I am using breeze to populate a list of projects to be displayed in a table. There is way more info than what I actually need so I am doing a projection on the data. I want to add a knockout computed onto the entity. So to accomplish this I registered and entity constructor like so...
metadataStore.registerEntityTypeCtor(entityNames.project, function () { this.isPartial = false; }, initializeProject);
The initializeProject function uses some of the values in the project to determine what the values should be for the computed. For example if the Project.Type == "P" then the rowClass should = "Red".
The problem I am having is that all the properties of Project are null except for the ProjNum which happens to be the key. I believe the issue is because I am doing the projection because I have registered other initializers for other types and they work just fine. Is there a way to make this work?
EDIT: I thought I would just add a little more detail for clarification. The values of all the properties are set to knockout observables, when I interrogate the properties using the javascript debugger in Chrome the _latestValue of any of the properties is null. The only property that is set is the ProjNum which is also the entity key.
EDIT2: Here is the client side code that does the projection
var getProjectPartials = function (projectObservable, username, forceRemote) {
var p1 = new breeze.Predicate("ProjManager", "==", username);
var p2 = new breeze.Predicate("ApprovalStatus", "!=", "X");
var p3 = new breeze.Predicate("ApprovalStatus", "!=", "C");
var select = 'ProjNum,Title,Type,ApprovalStatus,CurrentStep,StartDate,ProjTargetDate,CurTargDate';
var isQaUser = cookies.getCookie("IsQaUser");
if (isQaUser == "True") {
p1 = new breeze.Predicate("QAManager", "==", username);
select = select + ',QAManager';
} else {
select = select + ',ProjManager';
}
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select);
if (!forceRemote) {
var p = getLocal(query);
if (p.length > 1) {
projectObservable(p);
return Q.resolve();
}
}
return manager.executeQuery(query).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
var list = partialMapper.mapDtosToEntities(
manager,
data.results,
model.entityNames.project,
'ProjNum'
);
if (projectObservable) {
projectObservable(list);
}
log('Retrieved projects using breeze', data, true);
}
};
and the code for the partialMapper.mapDtosToEntities function.
var defaultExtension = { isPartial: true };
function mapDtosToEntities(manager,dtos,entityName,keyName,extendWith) {
return dtos.map(dtoToEntityMapper);
function dtoToEntityMapper(dto) {
var keyValue = dto[keyName];
var entity = manager.getEntityByKey(entityName, keyValue);
if (!entity) {
extendWith = $.extend({}, extendWith || defaultExtension);
extendWith[keyName] = keyValue;
entity = manager.createEntity(entityName, extendWith);
}
mapToEntity(entity, dto);
entity.entityAspect.setUnchanged();
return entity;
}
function mapToEntity(entity, dto) {
for (var prop in dto) {
if (dto.hasOwnProperty(prop)) {
entity[prop](dto[prop]);
}
}
return entity;
}
}
EDIT3: Looks like it was my mistake. I found the error when I looked closer at initializeProject. Below is what the function looked like before i fixed it.
function initializeProject(project) {
project.rowClass = ko.computed(function() {
if (project.Type == "R") {
return "project-list-item info";
} else if (project.Type == "P") {
return "project-list-item error";
}
return "project-list-item";
});
}
the issue was with project.Type I should have used project.Type() since it is an observable. It is a silly mistake that I have made too many times since starting this project.
EDIT4: Inside initializeProject some parts are working and others aren't. When I try to access project.ProjTargetDate() I get null, same with project.StartDate(). Because of the Null value I get an error thrown from the moment library as I am working with these dates to determine when a project is late. I tried removing the select from the client query and the call to the partial entity mapper and when I did that everything worked fine.
You seem to be getting closer. I think a few more guard clauses in your initializeProject method would help and, when working with Knockout, one is constantly battling the issue of parentheses.
Btw, I highly recommend the Knockout Context Debugger plugin for Chrome for diagnosing binding problems.
Try toType()
You're working very hard with your DTO mapping, following along with John's code from his course. Since then there's a new way to get projection data into an entity: add toType(...) to the end of the query like this:
var query = entityQuery
.from('Projects')
.where(p1.and(p2).and(p3))
.select(select)
.toType('Project'); // cast to Project
It won't solve everything but you may be able to do away with the dto mapping.
Consider DTOs on the server
I should have pointed this out first. If you're always cutting this data down to size, why not define the client-facing model to suit your client. Create DTO classes of the right shape(s) and project into them on the server before sending data over the wire.
You can also build metadata to match those DTOs so that Project on the client has exactly the properties it should have there ... and no more.
I'm writing about this now. Should have a page on it in a week or so.

Is it bad to add JSON on HTML data attribute?

Since HTML data attribute allows adding any custom data, I wonder if it is a good idea to include a set of JSON list as a data attribute? Then, the corresponding JSON can be easily accessed by JavaScript events with getAttribute("data-x").
In fact, my question is that: Is it standard, efficient, and reasonable to add a large set of data to an HTML attribute?
For example
<div data-x="A LARGE SET OF JSON DATA" id="x">
Or a large set of JSON data must be stored within <script> tag, and an HTML attribute is not a right place for a large set of data, even for data attribute.
Instead of storing everything in the data attribute you could use an identifier to access the data.
So for example you could do this :
var myBigJsonObj = {
data1 : { //lots of data},
data2 : { //lots of data}
};
and then you had some html like so :
<div data-dataId="data1" id="x">
You can use jquery to get the data now like so :
var dataId = $('#x').attr('data-dataId');
var myData = myBigJsonObj[dataId];
This is the best approach imho.
Say you want to save the object var dataObj = { foo: 'something', bar: 'something else' }; to an html data attribute.
Consider first stringifying the object such that we have
var stringifiedDataObj = JSON.stringify(dataObj);
Then you can use jQuery to set the stringifiedDataObj as the data attribute e.g. with the jQuery.data() API
While there's nothing to stop you embedding a long string of JSON in a data attribute, arguably the more "correct" way of doing it would be to add one data attribute per property of the JSON data. eg:
Javascript:
var dataObj = { foo: 'something', bar: 'something else' }
HTML:
<div data-foo="something" data-bar="something else"></div>
This way each piece of data in the JSON object corresponds to a separate, independently-accessible piece of data attached to the DOM element.
Bear in mind that either way you'll need to escape the values you're inserting into the HTML - otherwise stray " characters will break your page.
Technically you can, and I have seen several sites do this, but another solution is to store your JSON in a <script> tag and put a reference to the script tag in the data attribute. This is a better solution than just storing the data as a JS object in an actual script if the data is rendered to the page server-side, but there are CSP restrictions on inline script tags, for example.
HTML
<div data-data-id="#MyScriptData" id="x"></div>
<script type="application/json" id="MyScriptData">
{
"fruit": "apple",
...
}
</script>
JS
$(function () {
var dataId = $("#x").data("data-id");
var dataTag = $(dataId);
var dataJson = dataTag.html(); // returns a string containing the JSON data
var data = JSON.parse(dataJson);
...
});
You could make use of Map. Where your element will be the key, and the value at that key could be an object in which you store wanted data. Something like this (not tested though):
(function(global) {
const map = new Map();
global.CustomData = {
add(element, key, data) {
if (!map.has(element)) {
map.set(element, {});
}
map.get(element)[key] = data;
return map.get(element);
},
get(element, key) {
if (!map.has(element)) {
return null;
}
if (key !== undefined) {
return map.get(element)[key];
}
return map.get(element)
},
remove(element, key) {
if (!map.has(element)) {
return false;
}
delete map.get(element)[key];
if (Object.keys(map.get(element)).length === 0) {
map.delete(element);
}
return true;
},
clear(element) {
if (!map.has(element)) {
return false;
}
map.delete(element);
return true;
}
}
})(window);

Categories

Resources