I have a collection of elements that forms a net of measurements and are related in a recursive manner. I need to store them in a class of objects for storing in a Cassandra database. I have built the individual classes for creating each object. However, I am trying to figure out how to convert them from the JSON object to the classes. I cannot use a third party library for this unfortunately due to some restrictions with using unvetted libraries. Here is the structure: The goal is to have a list of PNodes the super class.
type classification = 'ADVERSE' | 'MODEST' | 'EXTREME';
interface PNode {
classification?: string;
qualityMetric?: number;
nextPhase?: PNode;
}
interface AlphaNode {
beta?: PNode;
gamma?: PNode;
nodeOptions: Array<PNode>;
}
interface BetaNode extends PNode {
ranking: number;
}
interface GammaNode extends PNode {
effectMetric: number;
}
Here is a sample of input data:
{
"pnodes": [
{
"betaNode": {
"classification": "ADVERSE",
"qualityMetric": 5,
"ranking": 3,
"nextPhase": {
"gammaNode": {
"classification": "MODEST",
"effectMetric": 2.2
}
}
}
},
{
"gammaNode": {
"nodeOptions": [
{
"betaNode": {
"classification": "EXTREME",
"qualityMetric": 5,
"ranking": 3,
"nextPhase": {
"alphaNode": {
"betaNode": {
"classification": "ADVERSE",
"effectMetric": 1.3
}
}
}
}
}
]
}
}
]
}
CodePudding user response:
Preface
Ok after wracking my brain about what exactly you are looking for in your question - I think I may understand and have a solution.
To my understanding you want to take this JSON input as a string, parse it into a javascript object and convert this object to use a nested class
structure defined by the types you defined above. If that was not your intent I have no clue what was.
Defining desired output types
Interfaces
First of all, I want to define the desired outputs as interfaces to simplify the logic, then we will implement these in class form next. These interfaces are prefixed with an I
to differentiate them from the classes (i.e. IBetaNode
). However, the interface types you provided do not work for the JSON
input data for a few reasons.
The nextPhase
of the IPNode
cannot be IPNode
because this would assume either the nextPhase
cannot be an alpha node or that all the possible node classes for alpha, beta and gamma, extend from IPNode
. The first is not the case as the example input has an alphaNode
as a nextPhase
, the second is true for gamma and beta but not alpha. So nextPhase
must be a union of alpha or beta or gamma. This also applies to nodeOptions
of the alpha node interface.
The also the beta
and gamma
types on the alpha node can be stricter because they are bound to one type of node.
type Classification = 'ADVERSE' | 'MODEST' | 'EXTREME';
interface IPNode {
classification?: Classification;
qualityMetric?: number;
nextPhase?: IAlphaNode | IBetaNode | IGammaNode;
}
interface IAlphaNode {
beta?: IBetaNode; // always BetaNode
gamma?: IGammaNode; // always GammaNode
nodeOptions?: Array<IAlphaNode | IBetaNode | IGammaNode>;
}
interface IBetaNode extends IPNode {
ranking: number;
}
interface IGammaNode extends IPNode {
effectMetric: number;
}
Classes
The classes are effectively the same as the interfaces notice each class implements
the respective interface
defined above. All properties applied when the class is instantiated, you could mutate values after but that is not necessary for the provided solution. One minor difference here is that the nodeOptions
on the AlphaNode
class is a required property but the constructor and IAlphaNode
permit it as optional which would simply defaults to []
.
class PNode implements IPNode {
classification?: Classification;
qualityMetric?: number;
nextPhase?: AlphaNode | BetaNode | GammaNode;
constructor(classification?: Classification, qualityMetric?: number, nextPhase?: AlphaNode | BetaNode | GammaNode) {
this.classification = classification;
this.qualityMetric = qualityMetric;
this.nextPhase = nextPhase;
}
}
class AlphaNode implements IAlphaNode {
beta?: BetaNode;
gamma?: GammaNode;
nodeOptions: Array<AlphaNode | BetaNode | GammaNode>;
constructor(beta?: BetaNode, gamma?: GammaNode, nodeOptions: Array<AlphaNode | BetaNode | GammaNode> = []) {
this.beta = beta
this.gamma = gamma
this.nodeOptions = nodeOptions
}
}
class BetaNode extends PNode implements IBetaNode {
ranking: number;
constructor(ranking:number, classification?: Classification, qualityMetric?: number, nextPhase?: AlphaNode | BetaNode | GammaNode) {
super(classification, qualityMetric, nextPhase);
this.ranking = ranking;
}
}
class GammaNode extends PNode implements IGammaNode {
effectMetric: number;
constructor(effectMetric:number, classification?: Classification, qualityMetric?: number, nextPhase?: AlphaNode | BetaNode | GammaNode) {
super(classification, qualityMetric, nextPhase);
this.effectMetric = effectMetric;
}
}
With all of the required output class types defined, our expected final output type is a list of all the base node as defined below.
type FinalOutput = Array<AlphaNode | BetaNode | GammaNode>;
Defining input type from JSON structure
Though not strictly necessary, doing so helps tremendously with the aid of intellisense when implementing the recursive logic. For the sake of isolation, I defined these input types inside a namespace
called Input
.
The types of the input are, in large part, similar to the output types with a few major differences.
All
PNodes
are nested inside objects with their respective node type as a key. This changes how the interfaces can be extended and thus the use of the&
over top-levelextends
.Node key naming is different on alpha class interface (i.e.
'beta'
instead of'betaNode'
)
In addition to these structural differences I added the NodeTypeKeys
enum
to keep the usage of these keys consistent. And finally added the JSON
type to represent the input JSON string type.
namespace Input {
export enum NodeTypeKeys {
Alpha = 'alphaNode',
Beta = 'betaNode',
Gamma = 'gammaNode',
};
export type IPNode = IAlphaNode | IBetaNode | IGammaNode;
interface IPNodeBase {
classification?: Classification;
qualityMetric?: number;
nextPhase?: IPNode;
}
export interface IAlphaNode {
[NodeTypeKeys.Alpha]: { // node type as a key
[NodeTypeKeys.Beta]?: IBetaNode[NodeTypeKeys.Beta];
[NodeTypeKeys.Gamma]?: IGammaNode[NodeTypeKeys.Gamma];
nodeOptions?: Array<IPNode>; // not always present in JSON input
}
}
export interface IBetaNode {
[NodeTypeKeys.Beta]: { // node type as a key
ranking: number;
} & IPNodeBase
}
export interface IGammaNode {
[NodeTypeKeys.Gamma]: { // node type as a key
effectMetric: number;
} & IPNodeBase
}
export interface JSON {
pnodes: Array<IPNode>;
}
}
Confirming JSON input
We can confirm these types on the provided JSON input using typescript with the JSON as a literal value. Doing so leads to yet another problem with you question... Two of the node types do not conform to your expected output node types