playground
I have a bunch of interfaces, at least 2-3 levels nested, where some of the leafs are numbers/strings, etc, but others are (numeric) enums.
I don't want to change this.
Now I want to "serialize" objects that implements my interfaces as JSON. Using JSON.stringify is good for almost all cases, but the enums, that are serialized with their (numerical) value.
I know that it's possible to pass a replacer function to JSON.stringify, but I'm stuck, as I'm not sure how to write a function that detect the structure of my object and replace the enum values with the appropriate names.
example:
enum E { X = 0, Y = 1, Z = 2 }
enum D { ALPHA = 1, BETA = 2, GAMMA = 3 }
interface C { e: E; }
interface B { c?: C; d?: D; }
interface A { b?: B; }
function replacer(this: any, key: string, value: any): any {
return value;
}
function stringify(obj: A): string {
return JSON.stringify(obj, replacer);
}
const expected = '{"b":{"c":{"e":"Y"},"d":"ALPHA"}}';
const recieved = stringify({ b: { c: { e: E.Y }, d: D.ALPHA } });
console.log(expected);
console.log(recieved);
console.log(expected === recieved);
It's not possible to automatically find out which enum was assigned to a field, not even with typescript's emitDecoratorMetadata option. That option can only tell you it's a Number and it will only be emitted on class fields that have other decorators on them.
The best solution you have is to manually add you own metadata. You can do that using reflect-metadata node module.
You'd have to find all enum fields on all of your classes and add metadata saying which enum should be used for serializing that field.
import 'reflect-metadata';
enum E
{
ALPHA = 1,
BETA = 2,
GAMMA = 3,
}
class C
{
// flag what to transform during serialization
#Reflect.metadata('serialization:type', E)
enumField: E;
// the rest will not be affected
number: number;
text: string;
}
This metadata could be added automatically if you can write an additonal step for your compiler, but that is not simple to do.
Then in your replacer you'll be able to check if the field was flagged with this matadata and if it is then you can replace the numeric value with the enum key.
const c = new C();
c.enumField= E.ALPHA;
c.number = 1;
c.text = 'Lorem ipsum';
function replacer(this: any, key: string, value: any): any
{
const enumForSerialization = Reflect.getMetadata('serialization:type', this, key);
return enumForSerialization ? enumForSerialization[value] ?? value : value;
}
function stringify(obj: any)
{
return JSON.stringify(obj, replacer);
}
console.log(stringify(c)); // {"enumField":"ALPHA","number":1,"text":"Lorem ipsum"}
This only works with classes, so you will have to replace your interfaces with classes and replace your plain objects with class instances, otherwise it will not be possible for you to know which interface/class the object represents.
If that is not possible for you then I have a much less reliable solution.
You still need to list all of the enum types for all of the fields of all of your interfaces.
This part could be automated by parsing your typescript source code and extracting the enum types for those enum fields and then saving it in a json file that you can load in runtime.
Then in the replacer you can guess the interface of an object by checking what are all of the fields on the this object and if they match an interface then you can apply enum types that you have listed for that interface.
Did you want something like this? It was the best I could think without using any reflection.
enum E { X = 0, Y = 1, Z = 2 }
enum D { ALPHA = 1, BETA = 2, GAMMA = 3 }
interface C { e: E; }
interface B { c?: C; d?: D; }
interface A { b?: B; }
function replacer(this: any, key: string, value: any): any {
switch(key) {
case 'e':
return E[value];
case 'd':
return D[value];
default:
return value;
}
}
function stringify(obj: A): string {
return JSON.stringify(obj, replacer);
}
const expected = '{"b":{"c":{"e":"Y"},"d":"ALPHA"}}';
const recieved = stringify({ b: { c: { e: E.Y }, d: D.ALPHA } });
console.log(expected);
console.log(recieved);
console.log(expected === recieved);
This solution assumes you know the structure of the object, just as you gave in the example.
Is there an elegant way to access the first property of an object...
where you don't know the name of your properties
without using a loop like for .. in or jQuery's $.each
For example, I need to access foo1 object without knowing the name of foo1:
var example = {
foo1: { /* stuff1 */},
foo2: { /* stuff2 */},
foo3: { /* stuff3 */}
};
var obj = { first: 'someVal' };
obj[Object.keys(obj)[0]]; //returns 'someVal'
Object.values(obj)[0]; // returns 'someVal'
Using this you can access also other properties by indexes. Be aware tho! Object.keys or Object.values return order is not guaranteed as per ECMAScript however unofficially it is by all major browsers implementations, please read https://stackoverflow.com/a/23202095 for details on this.
Try the for … in loop and break after the first iteration:
for (var prop in object) {
// object[prop]
break;
}
You can also do Object.values(example)[0].
You can use Object.values() to access values of an object:
var obj = { first: 'someVal' };
Object.values(obj)[0]; // someVal
Use Object.keys to get an array of the properties on an object. Example:
var example = {
foo1: { /* stuff1 */},
foo2: { /* stuff2 */},
foo3: { /* stuff3 */}
};
var keys = Object.keys(example); // => ["foo1", "foo2", "foo3"] (Note: the order here is not reliable)
Documentation and cross-browser shim provided here. An example of its use can be found in another one of my answers here.
Edit: for clarity, I just want to echo what was correctly stated in other answers: the key order in JavaScript objects is undefined.
A one-liner version:
var val = example[function() { for (var k in example) return k }()];
There isn't a "first" property. Object keys are unordered.
If you loop over them with for (var foo in bar) you will get them in some order, but it may change in future (especially if you add or remove other keys).
The top answer could generate the whole array and then capture from the list. Here is an another effective shortcut
var obj = { first: 'someVal' };
Object.entries(obj)[0][1] // someVal
Solution with lodash library:
_.find(example) // => {name: "foo1"}
but there is no guarantee of the object properties internal storage order because it depends on javascript VM implementation.
Here is a cleaner way of getting the first key:
var object = {
foo1: 'value of the first property "foo1"',
foo2: { /* stuff2 */},
foo3: { /* stuff3 */}
};
let [firstKey] = Object.keys(object)
console.log(firstKey)
console.log(object[firstKey])
if someone prefers array destructuring
const [firstKey] = Object.keys(object);
No. An object literal, as defined by MDN is:
a list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}).
Therefore an object literal is not an array, and you can only access the properties using their explicit name or a for loop using the in keyword.
To get the first key name in the object you can use:
var obj = { first: 'someVal' };
Object.keys(obj)[0]; //returns 'first'
Returns a string, so you cant access nested objects if there were, like:
var obj = { first: { someVal : { id : 1} }; Here with that solution you can't access id.
The best solution if you want to get the actual object is using lodash like:
obj[_.first(_.keys(obj))].id
To return the value of the first key, (if you don't know exactly the first key name):
var obj = { first: 'someVal' };
obj[Object.keys(obj)[0]]; //returns 'someVal'
if you know the key name just use:
obj.first
or
obj['first']
This has been covered here before.
The concept of first does not apply to object properties, and the order of a for...in loop is not guaranteed by the specs, however in practice it is reliably FIFO except critically for chrome (bug report). Make your decisions accordingly.
I don't recommend you to use Object.keys since its not supported in old IE versions. But if you really need that, you could use the code above to guarantee the back compatibility:
if (!Object.keys) {
Object.keys = (function () {
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function (obj) {
if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object');
var result = [];
for (var prop in obj) {
if (hasOwnProperty.call(obj, prop)) result.push(prop);
}
if (hasDontEnumBug) {
for (var i=0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
}
}
return result;
}})()};
Feature Firefox (Gecko)4 (2.0) Chrome 5 Internet Explorer 9 Opera 12 Safari 5
More info: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys
But if you only need the first one, we could arrange a shorter solution like:
var data = {"key1":"123","key2":"456"};
var first = {};
for(key in data){
if(data.hasOwnProperty(key)){
first.key = key;
first.content = data[key];
break;
}
}
console.log(first); // {key:"key",content:"123"}
If you need to access "the first property of an object", it might mean that there is something wrong with your logic. The order of an object's properties should not matter.
A more efficient way to do this, without calling Object.keys() or Object.values() which returns an array:
Object.prototype.firstKey = function () {
for (const k in this) {
if (Object.prototype.hasOwnProperty.call(this, k)) return k;
}
return null;
};
Then you can use it like:
const obj = {a: 1, b: 2, c: 3}
console.log(obj.firstKey()) //=> 'a'
This doesn't necessarily return the first key, see Elements order in a "for (… in …)" loop
we can also do with this approch.
var example = {
foo1: { /* stuff1 */},
foo2: { /* stuff2 */},
foo3: { /* stuff3 */}
};
Object.entries(example)[0][1];
This is an old question but most of the solutions assume that we know the attribute's name which it is not the case for example if you are trying to visualize data from files that the user can upload or similar cases.
This is a simple function that I use and works in both cases that you know the variable and if not it will return the first attribute of the object (sorted alphabetically)
The label function receives an object d and extract the key if exits, otherwise returns the first attribute of the object.
const data = [
{ label: "A", value: 10 },
{ label: "B", value: 15 },
{ label: "C", value: 20 },
{ label: "D", value: 25 },
{ label: "E", value: 30 }
]
const keys = ['label', 0, '', null, undefined]
const label = (d, k) => k ? d[k] : Object.values(d)[0]
data.forEach(d => {
console.log(`first: ${label(d)}, label: ${label(d, keys[0])}`)
})
keys.forEach(k => {
console.log(`label of ${k}: ${label(data[0], k)}`)
})
For values like 0, '', null, and undefined will return the first element of the array.
this is my solution
const dataToSend = {email:'king#gmail.com',password:'12345'};
const formData = new FormData();
for (let i = 0; i < Object.keys(dataToSend).length; i++) {
formData.append(Object.keys(dataToSend)[i],
Object.values(dataToSend)[i]);
}
console.log(formData);
Any reason not to do this?
> example.map(x => x.name);
(3) ["foo1", "foo2", "foo3"]
Basic syntax to iterate through key-value gracefully
const object1 = {
a: 'somestring',
b: 42
};
for (const [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`);
}
// expected output:
// "a: somestring"
// "b: 42"
As others have pointed out, if the order of properties is important, storing them in an object is not a good idea. Instead, you should use an array via square brackets. E.g.,
var example = [ {/* stuff1 */}, { /* stuff2 */}, { /* stuff3 */}];
var first = example[0];
Note that you lose the 'foo' identifiers. But you could add a name property to the contained objects:
var example = [
{name: 'foo1', /* stuff1 */},
{name: 'foo2', /* stuff2 */},
{name: 'foo3', /* stuff3 */}
];
var whatWasFirst = example[0].name;
For those seeking an answer to a similar question, namely: "How do I find one or more properties that match a certain pattern?", I'd recommend using Object.keys(). To extend benekastah's answer:
for (const propName of Object.keys(example)) {
if (propName.startsWith('foo')) {
console.log(`Found propName ${propName} with value ${example[propName]}`);
break;
}
}
I have a large JSON object that serves a purpose of the tree source. within this JSON I need to find an object with a given ID and build the complete to it meaning collecting its parents. To illustrate it's like in the file system: folde1/folder2/folder3/file.txt.
Here is my recursive function to find the object:
private find(source, id): string[] {
for (const key in source)
{
var item = source[key];
if (item.id === id) {
return this.indexes;
}
if (item.children) {
this.indexes.push(key);
var subresult = this.find(item.children, id);
if (subresult) {
this.indexes.push(key);
return this.indexes;
}
}
}
return null;
}
My problem is that while looking for the object I am picking up a lot of false parents thus the result has extra data.
Any idea how make it work? I am also thinking to start looking for the path from the inside, but dont know how to get a parent of the found object.
Thanks for the help.
I'm going to make a general answer and hopefully it applies to your actual structure. I assume a Tree looks like this:
interface Tree {
id: string;
children?: Record<string, Tree>;
}
Each Tree has an id, and an optional children property which is a dictionary of Trees. A possible implementation of findPathById() follows:
function findPathById(tree: Tree, id: string, curPath: string[] = []): string[] | undefined {
if (tree.id === id) return curPath;
const children = tree.children ?? {}
for (let k in children) {
const possibleAnswer = findPathById(children[k], id, [...curPath, k]);
if (possibleAnswer) return possibleAnswer;
}
return undefined;
}
The approach here is that the function takes a parameter curPath corresponding to the path to the current tree node, to which subsequent path entries can be appended. If this path is left out, then it will be assumed to be empty. If the tree passed in has the desired id, then we've found it and can return curPath. Otherwise, we start checking each property of children (if it exists) to see if the id exists in one of the children, calling findPathId on the child value, with a new curPath made by appending the child key to the current path. If we get a result, we return it. Otherwise, we go on to the next child. If we haven't found the id after checking the current item and recursively down through its children, then it isn't in there, and we return undefined.
Here's a test:
const tree: Tree = {
id: "A",
children: {
x: {
id: "B", children: {
t: { id: "E" },
u: { id: "F" }
}
},
y: {
id: "C", children: {
v: { id: "G" },
w: { id: "H" }
}
},
z: { id: "D" }
}
}
console.log(findPathById(tree, "A")); // []
console.log(findPathById(tree, "D")); // ["z"]
console.log(findPathById(tree, "G")); // ["y", "v"]
console.log(findPathById(tree, "J")); // undefined
This looks like the behavior we want. The "A" id is found at the root, so the path is an empty array. The "D" id is found at tree.children.z, so the path is ["z"]. The "G" id is found at tree.children.y.children.v, so the path is ["y", "v"]. And finally, the "J" id is never found, so the path is undefined.
Playground link to code
I want to get my C#7 tuple property names in my JSON (Newtonsoft.Json) output.
My problem is:
When I want to convert my tuple to JSON format that not support my parameters names.
For example this is my "Test2" method and you can see the JSON output:
public void Test2()
{
var data = GetMe2("ok");
var jsondata = JsonConvert.SerializeObject(data);//JSON output is {"Item1":5,"Item2":"ok ali"}
}
public (int MyValue, string Name) GetMe2(string name)
{
return (5, name + " ali");
}
The JSON output is "{"Item1":5,"Item2":"ok ali"}" but i want "{"MyValue":5,"Name":"ok ali"}";
This is not impossible because I can get property names in runtime:
foreach (var item in this.GetType().GetMethods())
{
dynamic attribs = item.ReturnTypeCustomAttributes;
if (attribs.CustomAttributes != null && attribs.CustomAttributes.Count > 0)
{
foreach (var at in attribs.CustomAttributes)
{
if (at is System.Reflection.CustomAttributeData)
{
var ng = ((System.Reflection.CustomAttributeData)at).ConstructorArguments;
foreach (var ca in ng)
{
foreach (var val in (IEnumerable<System.Reflection.CustomAttributeTypedArgument>)ca.Value)
{
var PropertyNameName = val.Value;
Console.WriteLine(PropertyNameName);//here is property names of C#7 tuple
}
}
}
}
dynamic data = attribs.CustomAttributes[0];
var data2 = data.ConstructorArguments;
}
}
For the specific case here, it is impossible. That's because SerializeObject has no way of finding out where the tuple came from, all it sees is ValueTuple<int, string>.
The situation would be different if you were serializing an object with tuple properties, in which case SerializeObject could use reflection to find the TupleElementNames attributes (even though it currently doesn't).
The short answer it that tuples don't have properties.
A tuple is a bag of values used, mainly, to return multiple values from a method.
They were never intended to model entities.
The only way to solve your problem, if you don't want to create a type for that, is:
public void Test2()
{
var data = GetMe2("ok");
var jsondata = JsonConvert.SerializeObject(new { data.MyValue, data.Name });//JSON output is {"Item1":5,"Item2":"ok ali"}
}
Hi I have a scenario where i need to compare attributes of two JSON objects, and if they are the same i want to append the value of one attribute to another one, is it possible to do it in this way?
ex:
JsonObject1
{
"FirstName" :
}
JsonObject2
{
"FirstName: "X"
}
Now since JsonObject2 has "X" and both Json objects have the same attribute i want to append "X" to FirstName in JsonObject1
Consider:
A = { name: null, age: 15 };
B = { name: "tom", age: 15 };
for (var key in A) {
if (A[key]) {
// all ok A already has a good value
} else if (key in B) {
// replace value in A with the one from B (possibly another bad value)
A[key] = B[key];
}
}
console.log(A);
// outputs { name: "tom", age: 15 }
This assumes A has all keys of B and that no value is 0 or elsewhat that should evaluate to false in the if clause.