Question
· Dec 10, 2019

Using %JSON.Adaptor to Export Complex Objects with relationships of 'many to many' type

Hi,

The class %JSON.Adaptor is very convenient,

I wonder if anyone faced the following scenario using it-

Exporting an object, that has a many to many relationship, and getting the details of the related objects.

For example, if we have three classes - Teachers, Students, and an intermediate class TeacherStudent .

The classes extend %JSON.Adaptor, and we would like to open a Student object, use %JSONExport, and get a JSON

That  includes the details of the student and all the teachers that teach the student, something like:

{
    "Id": "2",
    "Name": "Noname",
    "Teachers": [{
            "Id": "413",
            "Name": "Mr A",
        }, {
            "Id": "414",
            "Name": "Mr B",
        }
    ]
}
 

By default this is not the case, we get a list of Id's of the intermediate table:

{
    "Id": "2",
    "Name": "Noname",
    "Teachers": [{
            "Id": "1",
        }, {
            "Id": "2",
        }
    ]
}
 

Is there any way to achieve what I described? I looked at JSON mappings documentation, that does not seem to offer a solution.

 

Regards,

Nael Naseraldeen

Discussion (6)1
Log in or sign up to continue

Hi,

Ok. here is the code, it's a minimal code of a many to many relationship:

Class TafnitTest.Student Extends (%Persistent, %JSON.Adaptor, %Populate)
{
Property MyName As %String;
Relationship Teachers As TafnitTest.TechersToStudents [ Cardinality = many, Inverse = Student ];
}
 

Class TafnitTest.Teacher Extends (%Persistent, %JSON.Adaptor, %Populate)
{

Property MyName As %String;

Relationship Students As TafnitTest.TechersToStudents [ Cardinality = many, Inverse = Tracher ];

}

Class TafnitTest.TechersToStudents Extends (%Persistent, %JSON.Adaptor, %Populate)
{

Relationship Student As TafnitTest.Student [ Cardinality = one, Inverse = Teachers ];

Index StudentIndex On Student;

Relationship Tracher As TafnitTest.Teacher [ Cardinality = one, Inverse = Students ];

Index TracherIndex On Tracher;

}

Also I will attach the code as an XML export for convenience of importing and testing, If I find a way to do upload a file here..

after creating the classes, populate:

W ##CLASS(TafnitTest.Teacher).Populate(100)
W ##CLASS(TafnitTest.Student).Populate(100)
W ##CLASS(TafnitTest.TeachersToStudents).Populate(100)
 

And the JSON Export:

d ##CLASS(TafnitTest.Student).%OpenId(1).%JSONExport()
{"MyName":"O3183","Teachers":[{},{}]}
 

The JSON indicated that two teachers  teach this student, but not the details.

In my post, the id is a property I added to the intermediate class. I did not add it in this minimal example.

Thanks!

Nael Naseraldeen

Hi Nael,

I was able to make this work using an XData Map (Documentation here). I have uploaded my Sample to GitHub so you can see how I implemented it. I will also summarize what I did here:

This Sample uses 3 classes: Teacher, Student, and TeacherStudent. As you describe and as your JSON output suggests, Teacher and Student have a reference to TeacherStudent, which simply has an ID. To allow this to output the link to the next object (depending on the direction) I had to use Calculated properties. This means I can access data from TeacherStudent, but I am not storing the references again. The calculations to get the Teacher/Student are pretty simple, just an SQL statement to get the related record. At this point, if you try and use %JSONExport() on a student, it will print out the teachers as expected, but then the teachers print out all the students, and the students print the teachers again. This continues until reaching an error.

To avoid this, I used the XData Map. One map for "Student" and one map for "Teacher". Each is named based on the starting point. When using the Teacher map, the Teacher class will output the name and the link to TeacherStudent. TeacherStudent will then output the name and the link to Student. Student will then output only the name and will not link back to TeacherStudent. The same is true for using the Student map for Student (but going the other way).

I hope this accomplishes what you are looking for!

Peter

By the way, after running the populate method, the test method outputs the following:

SAMPLES>do ##class(DCSolutions.TwoWayJSONAdaptor.TeacherStudent).Test()
{"Name":"Peter","Teachers":[{"ID":1,"Teacher":{"Name":"Teacher1Name"}},{"ID":2,"Teacher":{"Name":"Teacher2Name"}}]}
{"Name":"Nael","Teachers":[{"ID":3,"Teacher":{"Name":"Teacher1Name"}},{"ID":4,"Teacher":{"Name":"Teacher3Name"}}]}
{"Name":"Teacher1Name","Students":[{"ID":1,"Student":{"Name":"Peter"}},{"ID":3,"Student":{"Name":"Nael"}}]}
{"Name":"Teacher2Name","Students":[{"ID":2,"Student":{"Name":"Peter"}}]}
{"Name":"Teacher3Name","Students":[{"ID":4,"Student":{"Name":"Nael"}}]}