I don't understand my mistake

In my code that I copy below, I get an error in the line GPedido.order.items &= QItems
Does not recognize “items”
I can’t figure out how to solve it.
where do I make the mistake?
thank you very much

                PROGRAM
INCLUDE('cjson.inc')

                MAP
                    TestPedido()
                END



CODE
    TestPedido()

TestPedido PROCEDURE
testString STRING(‘{{“pedido”:[{{“idPedido”:“2”,“idCliente”:“4”,“fecha”:“2023-10-10”,“estado”:“PENDIENTE”,“items”:[{{“idPedido”:“2”,“corubro”:“2”,“coarticulo”:“REF007”,“cantidad”:“1”},{{“idPedido”:“2”,“corubro”:“5”,“coarticulo”:“AA426”,“cantidad”:“1”},{{“idPedido”:“2”,“corubro”:“5”,“coarticulo”:“AA494”,“cantidad”:“1”}]}],“total”:1}’)
parser cJSONFactory

GPedido GROUP
pedido &QUEUE
total LONG
END

QPedido QUEUE
idPedido LONG
idCliente LONG
fecha STRING(20)
estado STRING(20)
items &QUEUE
END

QItems QUEUE
idPedido LONG
corubro SHORT
coarticulo STRING(20)
cantidad DECIMAL(11,4)
END

CODE
    
    FREE(QPedido)      
    FREE(QItems)
    GPedido.pedido &= QPedido
    GPedido.pedido.items &= QItems
    
    parser.ToGroup(CLIP(LEFT(testString)), GPedido, FALSE, '[{{"name":"pedido","instance":'& INSTANCE(QPedido,THREAD())&'}],[{{"name":"items","instance":'& INSTANCE(QItems,THREAD())&'}]')
               
    MESSAGE ('total: ' & GPedido.total & '   QPedido: ' & RECORDS(QPedido) &  '   QItems ' & RECORDS(QItems))

GPedido.pedido is declared as &QUEUE, i.e. as a reference to untyped QUEUE. Any queue can be assigned to such filed but no fields of assigned queue can be known for the compiler. Probably, you have to change the type of this field to &QPedido.

2 Likes

HI Carlos,

First I never did a reference of a QUEUE within a GROUP, I do not know if it does work, I know it is working all the way around referencing a GROUP within a QUEUE, and does work referencing a QUEUE within a QUEUE and a CLASS. I think I understand what you want to do to have a running total within the group.

Creating the following GROUP structure with the extracted fields from QPedido QUEUE

idPedido LONG
idCliente LONG
fecha STRING(20)
estado STRING(20)

Creating the following group structure
GPedidoHeader GROUP
idPedido LONG
idCliente LONG
fecha STRING(20)
estado STRING(20)
END

REPLACING the structure

GPedido GROUP
pedido &QUEUE
total LONG
END

QPedido QUEUE
idPedido LONG
idCliente LONG
fecha STRING(20)
estado STRING(20)
items &QUEUE
END

BY the following structure

GPedido GROUP structure
GPedidoHeader &GPedidoHeader
total LONG
END

QPedido QUEUE
GPedidoHeader &GPedidoHeader
items &QUEUE
END

This way you make sure the header within GPedido GROUP have a single header value. If it is a QUEUE the there is no way to get single Header value within.

The assignment maybe done as follows

GPedido.GPedidoHeader &= QPedido.GPedidoHeader

As @anon77170705 pointed out, you need to use TYPE’d queues vs generic &QUEUE
To clarify, I recoded what you showed

Changes to notice:

  • the QUEUEs are now QUEUE,TYPE
  • the order of the declarations have been changed to avoid forward declarations
  • the generic &QUEUE are now specific &QItems and &QPedido
  • the FREE()s have been remmed, as the queues are ,TYPE not instances
  • the references assignments are now NEWs
  • note your parser will need to be doing NEWs as well
    I left your use of INSTANCE in the parser.ToGroup call
    I don’t understand that call, but I’m pretty sure it will need to be corrected
  • The CleanUp.Destruct is automatically called after the procedure returns
    where we now FREE and DISPOSE the queues

To be clear, I’m not familiar with the cJSONFactory or how it works
It seems to me that it would need some sort of derived method, which allows you to NEW the child queue (items).

In which case the QUEUE,TYPE declarations should be moved to scope where they can be seen by that derived method. Remember ,TYPEs don’t take any memory, they’re just a description of what that label means.

TestPedido PROCEDURE()

CleanUp   CLASS 
Destruct     PROCEDURE
          END

QItems    QUEUE,TYPE  
idPedido    LONG
corubro     SHORT
coarticulo  STRING(20)
cantidad    DECIMAL(11,4)
          END


QPedido   QUEUE,TYPE
idPedido    LONG
idCliente   LONG
fecha       STRING(20)
estado      STRING(20)
items       &QItems 
          END


GPedido GROUP
pedido     &QPedido
total      LONG
          END

testString STRING(‘{{“pedido”:[{{“idPedido”:“2”,“idCliente”:“4”,“fecha”:“2023-10-10”,“estado”:“PENDIENTE”,“items”:[{{“idPedido”:“2”,“corubro”:“2”,“coarticulo”:“REF007”,“cantidad”:“1”},{{“idPedido”:“2”,“corubro”:“5”,“coarticulo”:“AA426”,“cantidad”:“1”},{{“idPedido”:“2”,“corubro”:“5”,“coarticulo”:“AA494”,“cantidad”:“1”}]}],“total”:1}’)
parser cJSONFactory
CODE
    
    ! FREE(QPedido)      
    ! FREE(QItems)
    GPedido.pedido       &= NEW QPedido
    GPedido.pedido.items &= NEW QItems
    
    parser.ToGroup(CLIP(LEFT(testString)), GPedido, FALSE,                               |
                       '[{{"name":"pedido","instance":'& INSTANCE(QPedido,THREAD())&'}]' |
                    &  ',[{{"name":"items","instance":'& INSTANCE( QItems,THREAD())&'}]' |
                  )
               
    MESSAGE (        'total: ' &         GPedido.total          |
              & '   QPedido: ' & RECORDS(GPedido.QPedido)       |
              &  '   QItems: ' & RECORDS(GPedido.QPedido.Items) |
            )


CleanUp.Destruct PROCEDURE 
   CODE 
   IF (GPedido.Pedido &= NULL)
       RETURN
   END 

   LOOP 
      GET(GPedido.Pedido, RECORDS(GPedido.Pedido))
      IF ERRORCODE() <> NoError
         BREAK 
      END 

      IF NOT    (GPedido.Pedido.Items &= NULL)
         FREE   (GPedido.Pedido.Items)
         DISPOSE(GPedido.Pedido.Items)
      END 

      DELETE( GPedido.Pedido )   
   END 
   DISPOSE( GPedido.Pedido )
2 Likes

Thank you all for the help you have given me. As soon as I find the solution I will share it

1 Like

I would change the definition of “items” inside QPedido to …

from items &QUEUE to items &QItems

Don’t make it too complicated. Make 2 queues

QPedido                         QUEUE
idPedido                          LONG
idCliente                         LONG
fecha                             STRING(20)
estado                            STRING(20)
                                END

QItems                          QUEUE
idPedido                          LONG
corubro                           SHORT
coarticulo                        STRING(20)
cantidad                          DECIMAL(11,4)
                                END

and load them separately.

Sorry Mike, I’m totally blocked.

I can’t understand how to queue separately, when QPedido contains QItems.

Root object contains “pedido” array (queue in Clarion), each pedido array item contains “items” array (queue as well). Get the root object, then obtain the pedido array, then call ToQueue(QPedido).
Then iterate each item in pedido, get the items array and call ToQueue(QItems).

I don’t know if it’s the best way, but there I was able to achieve what I need.

Thank you

        FREE(QItems)
       
        jRoot &= jParser.ParseFile('pedidos.json')
        
        MX# = jParser.ToQueue(CLIP(LEFT(jRoot.ToString())),'pedido', QPedido)
        json::DebugInfo(jRoot.ToString())
        
        resCount = jRoot.FindPathContext('$[pedido][*][items]', output)
        LOOP R# = 1 TO resCount
            GET(QPedido,R#)
            
            json::DebugInfo('--- Pedido --- ' & QPedido.idPedido & ' ' & QPedido.fecha)

            jItems &= output.GetObject(R#)
            MX# = jParser.ToQueue(CLIP(LEFT(jItems.ToString())),QItems)
            
            json::DebugInfo(jItems.ToString())
        END
        
        LOOP R# = 1 TO RECORDS(QItems)
            GET(QItems,R#)
            json::DebugInfo('--- QItems --- ' & QItems.idPedido & ' ' & QItems.coarticulo & ' ' & QItems.cantidad)
        END

Quite ineffective.

  • CLIP(LEFT(cjson.ToString())) is useless: cjson.ToString returns clipped and left aligned string.
  • jRoot &= jParser.ParseFile parses the json., then jParser.ToQueue parses same json second time.
  • jParser.ToQueue(jItems.ToString(), QItems): of course jItems.ToQueue(QItems) instead.
  • for me using FindPathContext here is overheaded. More simplier and efficient is the approach of array iteration as I described above.
1 Like

Hello !

I couldn’t understand how to get the “items” array by iterating the “Pedidos” array

I rewrote the code and I hope it is correct.
If there is anything to correct, I will be here! heh

Thank you so much


                    PROGRAM
                        INCLUDE('cjson.inc')
                    MAP
                        TestPedido()
                    END

    

    CODE
        TestPedido()
        
TestPedido          PROCEDURE        

QPedido                 QUEUE
idPedido                    LONG
idCliente                   LONG
fecha                       STRING(20)
estado                      STRING(20)
                        END

QItems                  QUEUE
idPedido                    LONG
corubro                     SHORT
coarticulo                  STRING(20)
cantidad                    DECIMAL(11,4)
                        END

jParser                 cJSONFactory
jRoot                   &cJSON
jPedidoArray            &cJSON
jPedido                 &cJSON
jItemsArray             &cJSON
i                       LONG,AUTO


    CODE
        
        FREE(QPedido)      
        FREE(QItems)
        
        jRoot &= jParser.ParseFile('pedidos.json')
        json::DebugInfo(jRoot.ToString())
        
        jPedidoArray &= jRoot.GetObjectItem('pedido',false)
        jPedidoArray.ToQueue(QPedido)        
        
        LOOP i = 1 TO RECORDS(QPedido)
            GET(QPedido,i)
            jPedido &= jPedidoArray.GetArrayItem(i)
            IF NOT jPedido &= NULL
                jItemsArray &= jPedido.GetObjectItem('items')
                IF NOT jItemsArray &= NULL
                    jItemsArray.ToQueue(QItems)
                END
            END
        END
        
        LOOP i = 1 TO RECORDS(QPedido)
            GET(QPedido,i)
            json::DebugInfo('--- Pedido --- : ' & i &' --- ' & QPedido.idPedido & ' ' & QPedido.fecha )        
        END
        
        LOOP i = 1 TO RECORDS(QItems)
            GET(QItems,i)
            json::DebugInfo('--- QItems --- ' & QItems.idPedido & ' ' & QItems.coarticulo & ' ' & QItems.cantidad)
        END
        
        jRoot.Delete()

Yes it is correct. You also can iterate json array instead of a QUEUE, but it is a matter of taste:

LOOP i=1 to jPedidoArray.GetArraySize()
  jPedido &= jPedidoArray.GetArrayItem(i)
  ...
END

bravo!! Hurrah !!
I will keep in mind the last thing you told me.
Thanks Mike and thanks to everyone who has helped me.

Congratulations on getting your code working.

For future issues, did you understand what was said about ,TYPEd queues vs using a generic &queue ?

I think I understand it, but if you have a document that explains it, it will be very helpful.

Bravo for your work!!!

The way I understand TYPEd declaration. I think of it as something that can be declared and defined but cannot be used directly. To use a TYPEd object of any kind there are two ways that I know, by instantiation and reference. We have to know the reference object is initializing in a given memory place, after its used within the procedure or class the object it must be disposed; if we do not dispose the object in reference to the type one, if we come to the same reference object afterwards we will encounter an access violation, this is hard to find when it occurs.

For example

QType QUEUE,TYPE
Name STRING(35)
END

by instantiation

Q QType

CODE

Q.Name = QType.Name

BY Reference

Q &QType

CODE

Q &= NEW QType

and at the end we must dispose the Q in reference to QType

DISPOSE(Q)

I forget to mention the generic &QUEUE maybe access directly.

Thank you very much for your help and explanation,

FYI - You can make your code look prettier on Clarionhub by using the “Preformatted Text” menu choice.

QType QUEUE,TYPE
Name    STRING(35)
      END

by instantiation

Q QType

  CODE

  Q.Name = QType.Name

BY Reference

Q &QType

   CODE

  Q &= NEW QType

and at the end we must dispose the Q in reference to QType

DISPOSE(Q)
2 Likes

Yes, when you use ,TYPE you are just describing a structure.
No memory is allocated.

To instantiate something based on the type.
You can use a reference and you can then NEW the type (unless it’s a GROUP,TYPE - for some reason you cannot new a group directly in CW)

oSomeType &SomeType
   CODE
   oSomeType &= NEW SomeType

You are correct that at some point all NEW’s should have a matching DISPOSE()
However it’s not always true that the DISPOSE has to happen in the same procedure.

It’s also true that when working with references it’s important to make sure that the reference is not null (if it’s at all possible that it can be null)

IF NOT ( oSomeType &= NULL )
    ! do something with the Instance
ELSE
   ! complain
END

You can also use
MySomeType LIKE( SomeType )

NOTE: when SomeType is a QUEUE,TYPE
then MySomeType will be a GROUP like the queue’s buffer

You can also declare GROUPs, QUEUEs and CLASSes with where you derive from something, which can be ,TYPEd structure.

SomeRefinedType     GROUP( SomeType ),TYPE
MoreStuff              LONG
                    END

SomeRefinedInstance GROUP( SomeType )
OtherStuff             LONG
                    END

Note you when you call DISPOSE( oSomeType )
The dispose command will cause .Destruct to be called (assuming it’s a class and it has a .destruct).
This dispose command will set oSomeType &= NULL

It is ok, to call DISPOSE( oSomeType ) when oSomeType is already NULL
it won’t do anything, but it won’t crash either. Therefore it’s reasonable to replace

IF NOT (oSomeType &= NULL) 
   DISPOSE( oSomeType )
END

with

DISPOSE( oSomeType )

4 Likes