Home > OS >  Best way to build a network of Nodes from JSON to classes
Best way to build a network of Nodes from JSON to classes

Time:08-29

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.

  1. 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-level extends.

  2. 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

  • Related