unit XData.Model.Deserializer;

{$I XData.Inc}

interface

uses
  SysUtils, Types, TypInfo,
  {$IFDEF PAS2JS}
  JS,
  {$ELSE}
  Bcl.Json.Classes,
  Bcl.Json,
  {$ENDIF}
  Bcl.Json.Common,
  Bcl.TypInfo.Common,
  XData.Model.Classes;

type
  EXDataModelDeserialization = class(Exception);

  TXDataModelDeserializer = class
  strict private
    FModel: TXDataModel;
    FRoot: TJObject;
  public
    constructor Create(AModel: TXDataModel; const Json: string); reintroduce;
    destructor Destroy; override;

    procedure Read;
    procedure ReadBase(Base: TXDataModelObject; JObj: TJObject);
    procedure ReadType(XDataType: TXDataType; JObj: TJObject);
    procedure ReadStructuredType(StructuredType: TXDataStructuredType; JObj: TJObject);
    procedure ReadScalarType(ScalarType: TXDataScalarType; JObj: TJObject);

    procedure ReadModel(Model: TXDataModel; JObj: TJObject);
    procedure ReadSchema(Schema: TXDataSchema; JObj: TJObject);
    procedure ReadEntityType(EntityType: TXDataEntityType; JObj: TJObject);
    procedure ReadSimpleProperty(SimpleProperty: TXDataSimpleProperty; JObj: TJObject);
    procedure ReadNavigationProperty(NavigationProperty: TXDataNavigationProperty; JObj: TJObject);
    procedure ReadProperty(Prop: TXDataProperty; JObj: TJObject);
    procedure ReadEntityContainer(Container: TXDataEntityContainer; JObj: TJObject);
    procedure ReadEntitySet(EntitySet: TXDataEntitySet; JObj: TJObject);
    procedure ReadEnumType(EnumType: TXDataEnumType; JObj: TJObject);
    procedure ReadEnumMember(EnumMember: TXDataEnumMember; JObj: TJObject);
    procedure ReadController(Controller: TXDataController; JObj: TJObject);
    procedure ReadAction(Action: TXDataAction; JObj: TJObject);
    procedure ReadParamDef(ParamDef: TXDataParamDef; JObj: TJObject);
  public
    class procedure Deserialize(AModel: TXDataModel; const Json: string);
  end;

implementation

{ TXDataModelDeserializer }

constructor TXDataModelDeserializer.Create(AModel: TXDataModel; const Json: string);
begin
  FModel := AModel;
  {$IFDEF PAS2JS}
  FRoot := TJSJSON.parseObject(Json);
  {$ELSE}
  FRoot := TJson.Deserialize<TJObject>(Json);
  {$ENDIF}
end;

class procedure TXDataModelDeserializer.Deserialize(AModel: TXDataModel; const Json: string);
var
  Deserializer: TXDataModelDeserializer;
begin
  Deserializer := TXDataModelDeserializer.Create(AModel, Json);
  try
    Deserializer.Read;
  finally
    Deserializer.Free;
  end;
end;

destructor TXDataModelDeserializer.Destroy;
begin
  {$IFNDEF PAS2JS}
  FRoot.Free;
  {$ENDIF}
  inherited;
end;

procedure TXDataModelDeserializer.ReadModel(Model: TXDataModel; JObj: TJObject);
var
  Key: string;
  I: Integer;
  JSchemas: TJArray;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(Model, JObj);
  Model.Schemas.Clear;
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Title' then
      Model.Title := JToStr(JObj[Key])
    else
    if Key = 'Version' then
      Model.Version := JToStr(JObj[Key])
    else
    if Key = 'Schemas' then
    begin
      JSchemas := AsJArray(JObj[Key]);
      for I := 0 to JSchemas.Length - 1 do
        ReadSchema(Model.Schemas[Model.Schemas.Add(TXDataSchema.Create)], AsJObject(JSchemas[I]));
    end;
  end;
end;

procedure TXDataModelDeserializer.ReadNavigationProperty(
  NavigationProperty: TXDataNavigationProperty; JObj: TJObject);
var
  Key: string;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadProperty(NavigationProperty, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Target' then
      NavigationProperty.TargetRef := JToStr(JObj[Key]);
  end;
end;

procedure TXDataModelDeserializer.ReadParamDef(ParamDef: TXDataParamDef;
  JObj: TJObject);
var
  Key: string;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(ParamDef, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Name' then
      ParamDef.Name := JToStr(JObj[Key])
    else
    if Key = 'BindingMode' then
      ParamDef.BindingMode := TBindingMode(GetEnumValue_(TypeInfo(TBindingMode), JToStr(JObj[Key])))
    else
    if Key = 'Output' then
      ParamDef.Output := JToBool(JObj[Key])
    else
    if Key = 'Input' then
      ParamDef.Input := JToBool(JObj[Key])
    else
    if Key = 'Type' then
      ParamDef.TypeRef := JToStr(JObj[Key]);
  end;
end;

procedure TXDataModelDeserializer.ReadProperty(Prop: TXDataProperty;
  JObj: TJObject);
var
  Key: string;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(Prop, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Name' then
      Prop.Name := JToStr(JObj[Key])
    else
    if Key = 'Required' then
      Prop.Required := JToBool(JObj[Key]);
  end;
end;

procedure TXDataModelDeserializer.Read;
begin
  ReadModel(FModel, FRoot);
  FModel.Prepare;
end;

procedure TXDataModelDeserializer.ReadAction(Action: TXDataAction;
  JObj: TJObject);
var
  Key: string;
  I: Integer;
  JParamDefs: TJArray;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(Action, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Name' then
      Action.Name := JToStr(JObj[Key])
    else
    if Key = 'Parameters' then
    begin
      JParamDefs := AsJArray(JObj[Key]);
      for I := 0 to JParamDefs.Length - 1 do
        ReadParamDef(Action.Parameters[Action.Parameters.Add(TXDataParamDef.Create)], AsJObject(JParamDefs[I]));
    end
    else
    if Key = 'HttpMethod' then
      Action.HttpMethod := JToStr(JObj[Key])
    else
    if Key = 'IsParamStream' then
      Action.IsParamStream := JToBool(JObj[Key])
    else
    if Key = 'IsResultStream' then
      Action.IsResultStream := JToBool(JObj[Key])
    else
    if Key = 'IsResultCriteria' then
      Action.IsResultCriteria := JToBool(JObj[Key])
    else
    if Key = 'IsResultSingleObject' then
      Action.IsResultSingleObject := JToBool(JObj[Key])
    else
    if Key = 'OperationId' then
      Action.OperationId := JToStr(JObj[Key]);
  end;
end;

procedure TXDataModelDeserializer.ReadBase(Base: TXDataModelObject;
  JObj: TJObject);
var
  Key: string;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Description' then
      Base.Description := JToStr(JObj[Key]);
  end;
end;

procedure TXDataModelDeserializer.ReadController(Controller: TXDataController;
  JObj: TJObject);
var
  Key: string;
  I: Integer;
  JActions: TJArray;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(Controller, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Name' then
      Controller.Name := JToStr(JObj[Key])
    else
    if Key = 'Actions' then
    begin
      JActions := AsJArray(JObj[Key]);
      for I := 0 to JActions.Length - 1 do
        ReadAction(Controller.Actions[Controller.Actions.Add(TXDataAction.Create)], AsJObject(JActions[I]));
    end;
  end;
end;

procedure TXDataModelDeserializer.ReadEntityContainer(
  Container: TXDataEntityContainer; JObj: TJObject);
var
  Key: string;
  I: Integer;
  JEntitySets: TJArray;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(Container, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Name' then
      Container.Name := JToStr(JObj[Key])
    else
    if Key = 'EntitySets' then
    begin
      JEntitySets := AsJArray(JObj[Key]);
      for I := 0 to JEntitySets.Length - 1 do
        ReadEntitySet(Container.EntitySets[Container.EntitySets.Add(TXDataEntitySet.Create)], AsJObject(JEntitySets[I]));
    end;
  end;
end;

procedure TXDataModelDeserializer.ReadEntitySet(EntitySet: TXDataEntitySet;
  JObj: TJObject);
var
  Key: string;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(EntitySet, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Name' then
      EntitySet.Name := JToStr(JObj[Key])
    else
    if Key = 'EntityType' then
      EntitySet.EntityTypeRef := JToStr(JObj[Key]); // Reference
  end;
end;

procedure TXDataModelDeserializer.ReadEntityType(EntityType: TXDataEntityType;
  JObj: TJObject);
var
  Key: string;
  I: Integer;
  JProperties: TJArray;
  JNavigationProperties: TJArray;
  JKey: TJArray;
  TempKeyRef: TStringDynArray;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadStructuredType(EntityType, JObj);
  SetLength(TempKeyRef, 0);
  EntityType.Properties.Clear;
  EntityType.NavigationProperties.Clear;
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Properties' then
    begin
      JProperties := AsJArray(JObj[Key]);
      for I := 0 to JProperties.Length - 1 do
        ReadSimpleProperty(EntityType.Properties[EntityType.Properties.Add(TXDataSimpleProperty.Create)], AsJObject(JProperties[I]));
    end
    else
    if Key = 'NavigationProperties' then
    begin
      JNavigationProperties := AsJArray(JObj[Key]);
      for I := 0 to JNavigationProperties.Length - 1 do
        ReadNavigationProperty(EntityType.NavigationProperties[EntityType.NavigationProperties.Add(TXDataNavigationProperty.Create)], AsJObject(JNavigationProperties[I]));
    end
    else
    if Key = 'BaseType' then
      EntityType.BaseTypeRef := JToStr(JObj[Key])
    else
    if Key = 'Key' then
    begin
      JKey := AsJArray(JObj[Key]);
      SetLength(TempKeyRef, JKey.Length);
      for I := 0 to JKey.Length - 1 do
        TempKeyRef[I] := JToStr(JKey[I]);
      EntityType.KeyRef := TempKeyRef;
    end
  end;
end;

procedure TXDataModelDeserializer.ReadEnumMember(EnumMember: TXDataEnumMember;
  JObj: TJObject);
var
  Key: string;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(EnumMember, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Name' then
      EnumMember.Name := JToStr(JObj[Key])
    else
    if Key = 'Value' then
      EnumMember.Value := JToInt(JObj[Key]);
  end;
end;

procedure TXDataModelDeserializer.ReadEnumType(EnumType: TXDataEnumType;
  JObj: TJObject);
var
  Key: string;
  I: Integer;
  JMembers: TJArray;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadScalarType(EnumType, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Members' then
    begin
      JMembers := AsJArray(JObj[Key]);
      for I := 0 to JMembers.Length - 1 do
        ReadEnumMember(EnumType.Members[EnumType.Members.Add(TXDataEnumMember.Create)], AsJObject(JMembers[I]));
    end;
  end;
end;

procedure TXDataModelDeserializer.ReadScalarType(ScalarType: TXDataScalarType;
  JObj: TJObject);
begin
  ReadType(ScalarType, JObj);
end;

procedure TXDataModelDeserializer.ReadSchema(Schema: TXDataSchema;
  JObj: TJObject);
var
  Key: string;
  I: Integer;
  JEntityTypes: TJArray;
  JEntityContainers: TJArray;
  JEnumTypes: TJArray;
  JControllers: TJArray;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(Schema, JObj);
  Schema.EntityTypes.Clear;
  Schema.EntityContainers.Clear;
  Schema.EnumTypes.Clear;
  Schema.Controllers.Clear;
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Namespace' then
      Schema.Namespace := JToStr(JObj[Key])
    else
    if Key = 'Alias' then
      Schema.Alias := JToStr(JObj[Key])
    else
    if Key = 'EntityTypes' then
    begin
      JEntityTypes := AsJArray(JObj[Key]);
      for I := 0 to JEntityTypes.Length - 1 do
        ReadEntityType(Schema.EntityTypes[Schema.EntityTypes.Add(TXDataEntityType.Create)], AsJObject(JEntityTypes[I]));
    end
    else
    if Key = 'EntityContainers' then
    begin
      JEntityContainers := AsJArray(JObj[Key]);
      for I := 0 to JEntityContainers.Length - 1 do
        ReadEntityContainer(Schema.EntityContainers[Schema.EntityContainers.Add(TXDataEntityContainer.Create)], AsJObject(JEntityContainers[I]));
    end
    else
    if Key = 'EnumTypes' then
    begin
      JEnumTypes := AsJArray(JObj[Key]);
      for I := 0 to JEnumTypes.Length - 1 do
        ReadEnumType(Schema.EnumTypes[Schema.EnumTypes.Add(TXDataEnumType.Create)], AsJObject(JEnumTypes[I]));
    end
    else
    if Key = 'Controllers' then
    begin
      JControllers := AsJArray(JObj[Key]);
      for I := 0 to JControllers.Length - 1 do
        ReadController(Schema.Controllers[Schema.Controllers.Add(TXDataController.Create)], AsJObject(JControllers[I]));
    end;
  end;
end;

procedure TXDataModelDeserializer.ReadSimpleProperty(
  SimpleProperty: TXDataSimpleProperty; JObj: TJObject);
var
  Key: string;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadProperty(SimpleProperty, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Length' then
      SimpleProperty.Length := JToInt(JObj[Key])
    else
    if Key = 'Precision' then
      SimpleProperty.Precision := JToInt(JObj[Key])
    else
    if Key = 'Scale' then
      SimpleProperty.Scale := JToInt(JObj[Key])
    else
    if Key = 'Type' then
      SimpleProperty.TypeRef := JToStr(JObj[Key]);
  end;
end;

procedure TXDataModelDeserializer.ReadStructuredType(
  StructuredType: TXDataStructuredType; JObj: TJObject);
begin
  ReadType(StructuredType, JObj);
end;

procedure TXDataModelDeserializer.ReadType(XDataType: TXDataType;
  JObj: TJObject);
var
  Key: string;
  Keys: TStringDynArray;
  KeyIndex: Integer;
begin
  ReadBase(XDataType, JObj);
  Keys := JObjectKeys(JObj);
  for KeyIndex := 0 to Length(Keys) - 1 do
  begin
    Key := Keys[KeyIndex];
    if Key = 'Name' then
      XDataType.Name := JToStr(JObj[Key]);
  end;
end;

end.
