how ignore property for serialization to JSON in Angular 7 - json

I have a navigation property in a hierarchical model structure that causes an circular dependency error in angular 7 during serialization.
export class MyClass {
// this property should be ignored for JSON serialization
parent: MyClass;
childList: MyClass[];
}
I wonder if there is any build-in solution (ex. a decorator like this exists for Jackson: #JsonBackReference) to ignore the parent property at serialization (ex. during http.put).
Thanks a lot for any advice!

if you prefer to handle this with a decorator you can make your own like this one
function enumerable(value: boolean) {
return function (target: any, propertyKey: string) {
let descriptor = Object.getOwnPropertyDescriptor(target, propertyKey) || {};
if (descriptor.enumerable != value) {
descriptor.enumerable = value;
Object.defineProperty(target, propertyKey, descriptor)
}
};
}
and then mark property as not enumerable like this
class MyClass {
#enumerable(false)
parent: MyClass;
}
other option is to redefine toJSON behavior
MyClass {
...
public toJSON() {
const {parent, ...otherProps} = this;
return otherProps;
}

Related

How to decode a JSON string to a given KClass?

I have my code structure like this:
File 1:
abstract class SomeClass {
abstract fun print()
companion object {
val versions = arrayOf(ClassV1::class, ClassV2::class)
}
}
#Serializable
data class ClassV1(val x: Int) : SomeClass() {
override fun print() {
println("Hello")
}
}
#Serializable
data class ClassV2(val y: String) : SomeClass() {
override fun print() {
println("World")
}
}
File 2:
fun <T : SomeClass> getSomeObject(json: String, kClass: KClass<T>): SomeClass {
return Json.decodeFromString(json)
}
fun printData(version: Int, json: String) {
val someClass: SomeClass = getSomeObject(json, SomeClass.versions[version])
someClass.print()
}
I have a json in printData that is a serialized form of some sub-class of SomeClass. I also have a version which is used to determine which class structure does the json represent. Based on the version, I want to de-serialize my json string to the appropriate sub-class of SomeClass.
Right now the getSomeObject function deserializes the json to SomeClass (which crashes, as expected). I want to know if there is a way I can deserialize it to the provided KClass.
I know I can do this like below:
val someClass = when (version) {
0 -> Json.decodeFromString<ClassV1>(json)
else -> Json.decodeFromString<ClassV2>(json)
}
But I am trying to avoid this since I can have a lot of such versions. Is there a better way possible?
It seems to me that the following is what you are looking for:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "version",
visible = false)
#JsonSubTypes(
JsonSubTypes.Type(value = ClassV1::class, name = "V1"),
JsonSubTypes.Type(value = ClassV2::class, name = "V2"))
abstract class SomeClass {
(...)
}
This basically means that your JSON would be deserialized as ClassV1 or ClassV2 based on the JSON property version:
V1 would mean that ClassV1 is the target class;
V2 would mean that ClassV2 is the target class.
You can find more information about this at the following online resources:
https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html
https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonSubTypes.Type.html
https://www.baeldung.com/jackson-annotations#jackson-polymorphic-type-handling-annotations

(de)serializing kotlin delegate properties with jackson

How can I (de)serialize kotlin delegate properties with jackson.
I have a class like this
class MyClass {
var a: Int = 42
set(value) {
val changed = field != value
field = value
if (changed) notifyListeners()
}
... and a dozen other properties that all follow this pattern ...
}
I wanted to simplify that by using
class MyClass {
var a: Int by NotifyUiOnChange(42)
...
private inner class NotifyUiOnChange<T>(initialValue: T) : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
notifyUiListeners()
}
}
}
but then Jackson will ignore that property.
How can I tell Jackson to serialize and deserialize that property anyway?
And how do I then apply #JsonIgnore annotations (or something comparable)?
You must use outdated version on Jackson (or maybe a version for Java, not Kotlin?). I've checked this using "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.+" (resolved to 2.10.1).
I've declared two classes:
class MyClass {
var a: Int = 42
set(value) {
val changed = field != value
field = value
if (changed) notifyListener(field)
}
private fun notifyListener(field: Any?) {
println("changed: $field")
}
}
class MyDelegatedClass {
var a: Int by NotifyUi(42)
private inner class NotifyUi<T>(initialValue: T) : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
notifyListener(newValue)
}
}
private fun notifyListener(field: Any?) {
println("changed: $field")
}
}
My main function:
fun main() {
val noDelegate = MyClass()
val delegated = MyDelegatedClass()
val mapper = ObjectMapper().registerKotlinModule()
// Deserialization
val noDelegateValue = mapper.writeValueAsString(noDelegate)
val delegatedValue = mapper.writeValueAsString(delegated)
println("No delegate:\t$noDelegateValue")
println("With delegate\t$delegatedValue")
// Serialization
val noDelegateObject = mapper.readValue<MyClass>("{\"a\":42}".trimIndent())
val delegateObject = mapper.readValue<MyDelegatedClass>("{\"a\":42}".trimIndent())
}
Output:
No delegate: {"a":42}
With delegate {"a":42}
changed: 42
We even can see output on delegate when we use delegate property :) (I believe it's a side-effect that should be consider as bug actually)
So, handling delegates is out of the box feature in jackson (I am not sure since when, but I used lazy delegate with jackson in older project I used to participate and there was no problems with delegates).
How to ignore delegated property?
So, you cannot apply JsonIgnore annotation to delegated field, because you will get This annotation is not applicable to target 'member property with delegate'. But, you can define the scope that annotation should be applied. Example below:
class MyDelegateClass {
#get:JsonIgnore // or set:
val a: Int by NotifyUi(42)
}
Unfortunately, seems that it's kind of broken, because you can use get: or set: and it's not apply to getter or setter only, but for both.

Parsing Complex JSON Objects with inheritance

I'm building a batch process that includes a number of steps of varying types.
export interface IStep {
id: number;
icon: string;
name: string;
selected: boolean;
}
export class InitStep implements IStep {
id: number;
icon: string;
name: string;
selected = false;
}
export class InputStep implements IStep {
id: number;
icon: string;
name: string;
selected = false;
primaryKey: string;
file: File;
}
export class QueryStep implements IStep {
constructor () {
this.filters = [];
this.output_fields = [];
this.table_fields = [];
const filter = new Filter;
this.filters.push(filter);
}
get input_ids(): number[] {
return this.filters.map(filter => filter.input_id);
}
id: number;
icon: string;
name: string;
selected = false;
table: string;
table_fields: string[];
filters: Filter[];
output_fields: string[];
}
export class OutputStep implements IStep {
constructor() {
this.fields = [];
}
id: number;
icon: string;
name: string;
selected = false;
fields: string[];
}
export class DeliveryStep implements IStep {
constructor() {
this.output_ids = [];
}
id: number;
icon: string;
name: string;
selected = false;
output_ids: number[];
format: BatchOutputType;
frequency: BatchFrequencyType;
email: string;
password: string;
}
I want to be able to have an array of any combination/number of these steps and be able to save them to and read from localstorage.
const key = 'notgunnawork';
localStorage.setItem(key, JSON.stringify(this.steps));
const s = JSON.parse(key) as IStep[];
I knew there was a snowball's chance in hell this was going to parse correctly, obviously the parser doesn't know which steps belong to what classes ultimately. I was just wondering if there was a simple way to get my array to come out looking the same way it went in. I'll eventually be posting this list to the server and would like my .Net Core code to also be able to parse this JSON without me having to make a custom parser.
EDIT
Added the full classes of what Im trying to serialize, for more detail. The error I'm getting whenever I try to serialize and then deserialize is: "Unexpected token o in JSON at position 1"
So, I'm going to answer what I think your issue is, and if I'm wrong then feel free to ignore me 🙂
Your problem is that you have a bunch of classes with methods but when you serialize instances of these to JSON and then deserialize them back, you end up with plain-old JavaScript objects and not instances of your classes. One way to handle this is to use a custom deserializer which knows about your classes and can "hydrate" or "revive" the plain-old JavaScript objects into genuine class instances. The JSON.parse() function allows you to specify a callback parameter called reviver which can be used to do just that.
First, we need to set up a system by which the reviver will know about your serializable classes. I'm going to use a class decorator which will add each class constructor to a registry the reviver can use. We will require that a serializable class constructor be assignable to a type we can call Serializable: it needs to have a no-argument constructor and the things it constructs need to have a className property:
// a Serializable class has a no-arg constructor and an instance property
// named className
type Serializable = new () => { readonly className: string }
// store a registry of Serializable classes
const registry: Record<string, Serializable> = {};
// a decorator that adds classes to the registry
function serializable<T extends Serializable>(constructor: T) {
registry[(new constructor()).className] = constructor;
return constructor;
}
Now, when you want to deserialize some JSON, you can check if the serialized thing has a className property that's a key in the registry. If so, you use the constructor for that classname in the registry, and copy properties into it via Object.assign():
// a custom JSON parser... if the parsed value has a className property
// and is in the registry, create a new instance of the class and copy
// the properties of the value into the new instance.
const reviver = (k: string, v: any) =>
((typeof v === "object") && ("className" in v) && (v.className in registry)) ?
Object.assign(new registry[v.className](), v) : v;
// use this to deserialize JSON instead of plain JSON.parse
function deserializeJSON(json: string) {
return JSON.parse(json, reviver);
}
Okay now that we have that, let's make some classes. (I'm using your original definitions here, before your edits.) Note that we are required to add a className property and we must have a no-arg constructor (this happens for free if you don't specify a constructor, since the default constructor is no-arg):
// mark each class as serializable, which requires a className and a no-arg constructor
#serializable
class StepType1 implements IStep {
id: number = 0;
name: string = "";
prop1: string = "";
readonly className = "StepType1"
}
#serializable // error, property className is missing
class OopsNoClassName {
}
#serializable // error, no no-arg constructor
class OopsConstructorRequiresArguments {
readonly className = "OopsConstructorRequiresArguments"
constructor(arg: any) {
}
}
#serializable
class StepType2 implements IStep {
id: number = 0;
name: string = "";
prop2: string = "";
prop3: string = "";
prop4: string = "";
readonly className = "StepType2"
}
#serializable
class StepType3 implements IStep {
id: number = 0;
name: string = "";
prop5: string = "";
prop6: string = "";
readonly className = "StepType3"
}
Now let's test it out. Make some objects as you would normally do, and put them in an array:
// create some objects of our classes
const stepType1 = new StepType1();
stepType1.id = 1;
stepType1.name = "Alice";
stepType1.prop1 = "apples";
const stepType2 = new StepType2();
stepType2.id = 2;
stepType2.name = "Bob";
stepType2.prop2 = "bananas";
stepType2.prop3 = "blueberries";
stepType2.prop4 = "boysenberries";
const stepType3 = new StepType3();
stepType3.id = 3;
stepType3.name = "Carol";
stepType3.prop5 = "cherries";
stepType3.prop6 = "cantaloupes";
// make an array of IStep[]
const arr = [stepType1, stepType2, stepType3];
And let's have a function which will examine the elements of an array and check to see if they are instances of your classes:
// verify that an array of IStep[] contains class instances
function verifyArray(arr: IStep[]) {
console.log("Array contents:\n" + arr.map(a => {
const constructorName = (a instanceof StepType1) ? "StepType1" :
(a instanceof StepType2) ? "StepType2" :
(a instanceof StepType3) ? "StepType3" : "???"
return ("id=" + a.id + ", name=" + a.name + ", instanceof " + constructorName)
}).join("\n") + "\n");
}
Let's make sure it works on arr:
// before serialization, everything is fine
verifyArray(arr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
Then we serialize it:
// serialize to JSON
const json = JSON.stringify(arr);
To demonstrate your original problem, let's see what happens if we just use JSON.parse() without a reviver:
// try to deserialize with just JSON.parse
const badParsedArr = JSON.parse(json) as IStep[];
// uh oh, none of the deserialized objects are actually class instances
verifyArray(badParsedArr);
// Array contents:
// id=1, name=Alice, instanceof ???
// id=2, name=Bob, instanceof ???
// id=3, name=Carol, instanceof ???
As you can see, the objects in badParsedArr do have the id and name properties (and the other class-specific instance properties like prop3 if you checked) but they are not instances of your classes.
Now we can see if the problem is fixed by using our custom deserializer:
// do the deserialization with our custom deserializer
const goodParsedArr = deserializeJSON(json) as IStep[];
// now everything is fine again
verifyArray(goodParsedArr);
// Array contents:
// id=1, name=Alice, instanceof StepType1
// id=2, name=Bob, instanceof StepType2
// id=3, name=Carol, instanceof StepType3
Yes, it works!
The above method is fine, but there are caveats. The main one: it will work if your serializable classes contain properties which are themselves serializable, as long as your object graph is a tree, where each object appears exactly once. But if you have an object graph with any kind of cycle in it (meaning that the same object appears multiple times if you traverse the graph multiple ways) then you will get unexpected results. For example:
const badArr = [stepType1, stepType1];
console.log(badArr[0] === badArr[1]); // true, same object twice
const badArrParsed = deserializeJSON(JSON.stringify(badArr));
console.log(badArrParsed[0] === baddArrParsed[1]); // false, two different objects
In the above case, the same object appears multiple times. When you serialize and deserialize the array, your new array contains two different objects with the same property values. If you need to make sure that you only deserialize any particular object exactly once, then you need a more complicated deserialize() function which keeps track of some unique property (like id) and returns existing objects instead of creating new ones.
Other caveats: this assumes your serializable classes have instance properties consisting only of other serializable classes as well as JSON-friendly values like strings, numbers, arrays, plain objects, and null. If you use other things, like Dates, you will have to deal with the fact that those serialize into strings.
Exactly how complicated serialization/deserialization is for you depends heavily on your use case.
Okay, hope that helps. Good luck!

Json property alias in typescript

Let's say I want to get a data from Visual Studio TFS and the response (as json) is in this kind of format:
{
"Microsoft.VSTS.Scheduling.StoryPoints": 3.0,
// ......
}
There's dot in the property name. Reading from other questions I found out that I can read that json in typescript by using an interface like this
export interface IStory { // I don't think this kind of interface do me any help
"Microsoft.VSTS.Scheduling.StoryPoints": number
}
And then I can use the property with this syntax:
var story = GetStoryFromTFS();
console.log(story["Microsoft.VSTS.Scheduling.StoryPoints"]);
But I'd prefer not to call the property like this, since the intellisense won't able to help me finding which property I want to use (because I call the property using a string).
In C# there is a JsonProperty attribute which enable me to create a model like this:
public class Story
{
[JsonProperty(PropertyName = "Microsoft.VSTS.Scheduling.StoryPoints")]
public double StoryPoints { get; set; }
}
And then I can use the property this way:
var story = GetStoryFromTFS();
Console.WriteLine(story.StoryPoints);
This way the intellisense will able to help me finding which property I want to use.
Is there something like JsonProperty attribute in typescript? Or is there any other, better way, to achieve this in typescript?
You have many options. Just keep in mind that all of these options require you to pass the original data to the class that will access it.
Map the values.
class StoryMap {
constructor(data: IStory) {
this.StoryPoints = data["Microsoft.VSTS.Scheduling.StoryPoints"];
}
StoryPoints: number;
}
Wrap the data.
class StoryWrap {
constructor(private data: IStory) {}
get StoryPoints(): number { return this.data["Microsoft.VSTS.Scheduling.StoryPoints"] };
}
Build a decorator to map the data.
function JsonProperty(name: string) {
return function DoJsonProperty(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.get = function () {
return this.data[name];
}
descriptor.set = function (value) {
this.data[name] = value;
}
}
}
class StoryDecorator
{
constructor(private data: IStory) {}
#JsonProperty("Microsoft.VSTS.Scheduling.StoryPoints")
get StoryPoints(): number { return 0 };
}

Using JSON.stringify in conjunction with TypeScript getter/setter

I am using getter/setter accessors in TypeScript. As it is not possible to have the same name for a variable and method, I started to prefix the variable with a lower dash, as is done in many examples:
private _major: number;
get major(): number {
return this._major;
}
set major(major: number) {
this._major = major;
}
Now when I use the JSON.stringify() method to convert the object into a JSON string, it will use the variable name as the key: _major.
As I don't want the JSON file to have all keys prefixed with a lower dash, is there any possibility to make TypeScript use the name of the getter method, if available? Or are there any other ways to use the getter/setter methods but still produce a clean JSON output?
I know that there are ways to manually modify the JSON keys before they are written to the string output. I am curious if there is simpler solution though.
Here is a JSFiddle which demonstrates the current behaviour.
No, you can't have JSON.stringify using the getter/setter name instead of the property name.
But you can do something like this:
class Version {
private _major: number;
get major(): number {
return this._major;
}
set major(major: number) {
this._major = major;
}
toJsonString(): string {
let json = JSON.stringify(this);
Object.keys(this).filter(key => key[0] === "_").forEach(key => {
json = json.replace(key, key.substring(1));
});
return json;
}
}
let version = new Version();
version.major = 2;
console.log(version.toJsonString()); // {"major":2}
based on #Jan-Aagaard solution I have tested this one
public toJSON(): string {
let obj = Object.assign(this);
let keys = Object.keys(this.constructor.prototype);
obj.toJSON = undefined;
return JSON.stringify(obj, keys);
}
in order to use the toJSON method
I think iterating through the properties and string manipulating is dangerous. I would do using the prototype of the object itself, something like this:
public static toJSONString() : string {
return JSON.stringify(this, Object.keys(this.constructor.prototype)); // this is version class
}
I've written a small library ts-typed, which generate getter/setter for runtime typing purpose. I've faced the same problem when using JSON.stringify(). So i've solved it by adding a kind of serializer, and proposing to implement a kind of toString (in Java) buy calling it toJSON.
Here is an example:
import { TypedSerializer } from 'ts-typed';
export class RuntimeTypedClass {
private _major: number;
get major(): number {
return this._major;
}
set major(major: number) {
this._major = major;
}
/**
* toString equivalent, allows you to remove the _ prefix from props.
*
*/
toJSON(): RuntimeTypedClass {
return TypedSerializer.serialize(this);
}
}
A new answer to an old question. For situations where there is no private field for a getter/setter, or where the private field name is different to the getter/setter, we can use the Object.getOwnPropertyDescriptors to find the get methods from the prototype.
https://stackoverflow.com/a/60400835/2325676
We add the toJSON function here so that it works with JSON.stringify as mentioned by other posters. This means we can't call JSON.stringify() within toJSON as it will cause an infinite loop so we clone using Object.assign(...)
I also removed the _private fields as a tidyup measure. You may want to remove other fields you don't want to incude in the JSON.
public toJSON(): any {
//Shallow clone
let clone: any = Object.assign({}, this);
//Find the getter method descriptors
//Get methods are on the prototype, not the instance
const descriptors = Object.getOwnPropertyDescriptors(Object.getPrototypeOf(this))
//Check to see if each descriptior is a get method
Object.keys(descriptors).forEach(key => {
if (descriptors[key] && descriptors[key].get) {
//Copy the result of each getter method onto the clone as a field
delete clone[key];
clone[key] = this[key]; //Call the getter
}
});
//Remove any left over private fields starting with '_'
Object.keys(clone).forEach(key => {
if (key.indexOf('_') == 0) {
delete clone[key];
}
});
//toJSON requires that we return an object
return clone;
}
Isn't dynamic but it work
export class MyClass{
text: string
get html() {
return this.text.toString().split("\n").map(e => `<p>${e}</p>`).join('');
}
toJson(): string {
return JSON.stringify({ ...this, html: this.html })
}
}
In calling
console.log(myClassObject.toJson())