2

New to Ada. Trying to work with some objects like the following {name:'Hermann',age:33} in an a project and I'd rather not write my own json parser for this. Is there either:

  • a way for me to configure Gnatcolls.JSON to parse and write these objects
    or
  • a different library I can use with json5 or javascript object literal support?
Inigo
  • 12,186
  • 5
  • 41
  • 70
KG6ZVP
  • 3,610
  • 4
  • 26
  • 45
  • JSON5 is a specification completely independent from JSON. Don't expect support for JSON5 from something that refers to JSON. I quickly scanned over JSON5 to check whether you could use [AdaYaml](https://ada.yaml.io/) to load it (since YAML is a superset of JSON) but apparently, JSON5 makes no effort to be compatible with YAML so this won't work either. I suggest preprocessing on a token level into JSON, and then piping it into a JSON processor; lexing+preprocessing JSON5 looks easy enough. – flyx Apr 05 '21 at 20:22
  • I wouldn't say it's "completely independent from JSON." json5.org bills it as a superset of JSON, but that doesn't mean it's independent. Quite honestly, I'm extremely annoyed the system I have to work with doesn't at least put out sane json, but here we are and I really do appreciate the feedback. I just wanted to pop a library in and be off :P – KG6ZVP Apr 05 '21 at 23:03
  • Well YAML is also a superset of JSON and yet no-one expects a JSON lib to parse YAML. That is what I meant by independent. If it was some kind of optional extension defined in the basic JSON specification, there would be a chance that JSON libraries supported it. – flyx Apr 06 '21 at 10:07
  • Okay, that makes sense. Looks like I'll be doing a bit more coding than I hoped, then. – KG6ZVP Apr 06 '21 at 10:20

1 Answers1

3

I wrote a JSON parser from the spec pretty quickly for work while doing other things, and it took a day/day-and-a-half; it's not particularly hard and I'll see about posting it to github or something.

However, JSON5 is different enough that re-implementing it would be on the order of the same difficulty as writing some sort of adaptor. Editing the parser to accept the new constructs might be more difficult than one might anticipate, as the IdentifierName allowed as a key means that you can't simply chain together the sequence (1) "get-open-brace", (2) "consume-whitespace", (3) "get-a-string", (4) "consume-whitespace", (5) "get-a-colon, (6) "consume-whitespace", (7) "get-JSON-object", (8) "consume-whitespace", (9) "get-a-character; if comma, go to #1, otherwise it should be an end-brace".

Perhaps one thing that makes things easier is to equate the stream- and string-operations so that you only have one production-method for your objects; there are three main ways to do this:

  1. Make a generic such that it takes a string and gives the profile for the stream-operation.

  2. Make a pair of overloaded functions that provide the same interface.

  3. Make a stream that is a string; the following does this:

    Package Example is
    -- String_Stream allows uses a string to buffer the underlying stream,
    -- it may be initialized with content from a string or a given length for
    -- the string underlying the stream.
    --
    -- This is intended for the construction and consumption of string-data
    -- using stream-operations
    Type String_Stream(<>) is new Ada.Streams.Root_Stream_Type with Private;
    
    Subtype Root_Stream_Class is Ada.Streams.Root_Stream_Type'Class;
    
    -- Create a String_Stream.
    Function "+"( Length : Natural ) return String_Stream;
    Function "+"( Text   : String  ) return String_Stream;
    Function "+"( Length : Natural ) return not null access Root_Stream_Class;
    Function "+"( Text   : String  ) return not null access Root_Stream_Class;
    
    -- Retrieve the remaining string-data; the (POSITION..DATA'LENGTH) slice.
    Function "-"( Stream : String_Stream ) return String;
    
    -- Retrieve the string-data; the (1..DATA'LENGTH) slice.
    Function Data(Stream : String_Stream ) return String;
    
    Private
    Pragma Assert( Ada.Streams.Stream_Element'Size = String'Component_Size );
    
    Overriding
    procedure Read
      (Stream : in out String_Stream;
       Item   :    out Ada.Streams.Stream_Element_Array;
       Last   :    out Ada.Streams.Stream_Element_Offset);
    
    Overriding
    procedure Write
      (Stream : in out String_Stream;
       Item   :        Ada.Streams.Stream_Element_Array);
    
    Type String_Stream(Length : Ada.Streams.Stream_Element_Count) is
      new Ada.Streams.Root_Stream_Type with record
        Data     : Ada.Streams.Stream_Element_Array(1..Length);
        Position : Ada.Streams.Stream_Element_Count;
      End record;
    End Example;
    

With implementation of:

Package Body Example is
    Use Ada.Streams;

    -------------------
    --  INITALIZERS  --
    -------------------

    Function From_String( Text   : String  ) return String_Stream
      with Inline, Pure_Function;
    Function Buffer     ( Length : Natural ) return String_Stream
      with Inline, Pure_Function;


    --------------
    --  R E A D --
    --------------

    Procedure Read
      (Stream : in out String_Stream;
       Item   :    out Ada.Streams.Stream_Element_Array;
       Last   :    out Ada.Streams.Stream_Element_Offset) is
        Use Ada.IO_Exceptions, Ada.Streams;
    Begin
        -- When there is a read of zero, do nothing.
        -- When there is a read beyond the buffer's bounds, raise an exception.
        --  Note: I've used two cases here-
        --      1) when the read is greater than the buffer,
        --      2) when the read would go beyond the buffer.
        -- Finally, read the given amount of data and update the position.
        if Item'Length = 0 then
            null;
        elsif Item'Length > Stream.Data'Length then
            Raise End_Error with "Request is larger than the buffer's size.";
        elsif Stream_Element_Offset'Pred(Stream.Position)+Item'Length > Stream.Data'Length then
            Raise End_Error with "Buffer will over-read.";
        else
            Declare
                Subtype Selection is Stream_Element_Offset range
                  Stream.Position..Stream.Position+Stream_Element_Offset'Pred(Item'Length);
            Begin
                Item(Item'Range):= Stream.Data(Selection);
                Stream.Position:= Stream_Element_Offset'Succ(Selection'Last);
                Last:= Selection'Last;--Stream.Position;
            End;
        end if;
    End Read;


    -----------------
    --  W R I T E  --
    -----------------

    Procedure Write
      (Stream : in out String_Stream;
       Item   :        Ada.Streams.Stream_Element_Array) is
    Begin
        Declare
            Subtype Selection is Stream_Element_Offset range
              Stream.Position..Stream.Position+Stream_Element_Offset'Pred(Item'Length);
        Begin
            Stream.Data(Selection):= Item(Item'Range);
            Stream.Position:= Stream_Element_Offset'Succ(Selection'Last);
        End;
    End Write;


    ----------------------------------
    --  INITALIZER IMPLEMENTATIONS  --
    ----------------------------------

    -- Create a buffer of the given length, zero-filled.
    Function Buffer( Length : Natural ) return String_Stream is
        Len : Constant Ada.Streams.Stream_Element_Offset :=
          Ada.Streams.Stream_Element_Offset(Length);
    Begin
        Return Result : Constant String_Stream:=
          (Root_Stream_Type with
           Position =>  1,
           Data     => (1..Len => 0),
           Length   =>  Len
          );
    End Buffer;

    -- Create a buffer from the given string.
    Function From_String( Text : String ) return String_Stream is
        Use Ada.Streams;

        Subtype Element_Range is Stream_Element_Offset range
          Stream_Element_Offset(Text'First)..Stream_Element_Offset(Text'Last);
        Subtype Constrained_Array  is Stream_Element_Array(Element_Range);
        Subtype Constrained_String is String(Text'Range);

        Function Convert is new Ada.Unchecked_Conversion(
           Source => Constrained_String,
           Target => Constrained_Array
          );
    Begin
        Return Result : Constant String_Stream:=
          (Root_Stream_Type with
           Position => Element_Range'First,
           Data     => Convert( Text ),
           Length   => Text'Length
          );
    End From_String;


    -- Classwide returning renames, for consistancy/overload.
    Function To_Stream( Text   : String  ) return Root_Stream_Class is
        ( From_String(Text) ) with Inline, Pure_Function;
    Function To_Stream( Length : Natural ) return Root_Stream_Class is
        ( Buffer(Length)    ) with Inline, Pure_Function;


    ----------------------------
    --  CONVERSION OPERATORS  --
    ----------------------------

    -- Allocating / access-returning initalizing operations.
    Function "+"( Length : Natural ) return not null access Root_Stream_Class is
      ( New Root_Stream_Class'(To_Stream(Length)) );
    Function "+"( Text   : String  ) return not null access Root_Stream_Class is
      ( New Root_Stream_Class'(To_Stream(Text))   );

    -- Conversion from text or integer to a stream; renaming of the initalizers.
    Function "+"( Text   : String  ) return String_Stream renames From_String;
    Function "+"( Length : Natural ) return String_Stream renames Buffer;

    -- Convert a given Stream_Element_Array to a String.
    Function "-"( Data   : Ada.Streams.Stream_Element_Array ) Return String is
        Subtype Element_Range is Natural range
          Natural(Data'First)..Natural(Data'Last);
        Subtype Constrained_Array  is Stream_Element_Array(Data'Range);
        Subtype Constrained_String is String(Element_Range);

        Function Convert is new Ada.Unchecked_Conversion(
           Source => Constrained_Array,
           Target => Constrained_String
          );

    Begin
        Return Convert( Data );
    End "-";


    ----------------------
    --  DATA RETRIEVAL  --
    ----------------------

    Function "-"( Stream : String_Stream ) return String is
    Begin
        Return -Stream.Data(Stream.Position..Stream.Length);
    End "-";


    Function Data(Stream : String_Stream ) return String is
    Begin
        Return -Stream.Data;
    End Data;


End Example;
Shark8
  • 4,095
  • 1
  • 17
  • 31