Nueva publicación

検索

Artículo
· 3 hr atrás Lectura de 5 min

JSON2Class, o conversor de JSON para ObjectScript que você sempre quis

Quantas vezes você teve que receber ou gerar um JSON e desejou poder trabalhar nele usando DTLs sem ter que lidar com DynamicObjects tentando lembrar o nome de cada campo? Você quer dividir e tornar seu arquivo JSON gigante mais digerível?

No meu caso, nunca, mas pensei que alguém poderia achar útil ter um recurso que captura seu JSON e o divide em uma série de classes ObjectScript com as quais você pode trabalhar de forma mais fácil e conveniente.

Pois bem... contemplem o JSON2Class em toda a sua glória!

Como o JSON2Class funciona?

É muito simples: ele aproveita os recursos do Embedded Python para fatiar e analisar seu JSON e gerar as classes associadas. Essas classes geradas serão de dois tipos:

  • %Persistent: para a classe principal ou raiz.
  • %SerialObject: para todas as subclasses que serão anexadas à principal.

Para fazer isso, ele leva em consideração os seguintes pontos:

Palavras reservadas

Nem todo nome de propriedade que vem no seu JSON é aceitável, então ele será modificado adicionando ao final do nome JSON, de modo que " language" se tornará " languageJSON".

Caracteres especiais

Caracteres especiais como "_" também são  removidos, então "patient_name" passará a ser chamado de "patientname".

%JSONFIELDNAME

Para evitar que a classe mude de nome quando for convertida de volta para JSON, o atributo %JSONFIELDNAME foi adicionado às propriedades cujos nomes são modificados, permitindo manter o rótulo original ao exportá-lo para JSON.

Gerando as classes

Toda a funcionalidade está contida na classe Utils.JSON2Class  , e para invocar o processo de geração temos o seguinte método:

ClassMethod Convert(
    json As %String,
    basePackage As %String = "App.Model",
    rootClassName As %String = "Root",
    outDir As %String = "/shared/generated"
) As %String

Vamos analisar os atributos do ClassMethod:

  • json: Este será nosso JSON modelo que queremos gerar.
  • basePackage: O pacote no qual todas as classes serão implementadas.
  • rootClassName: O nome da classe persistente que atuará como a raiz.
  • outDir: O diretório local onde os arquivos de classe serão gerados.

Exemplo:

Começamos com um JSON típico, como um recurso FHIR do tipo patient:

{
	"resourceType": "Patient",
	"id": "example",
	"identifier": [
		{
			"use": "usual",
			"type": {
				"coding": [
					{
						"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
						"code": "MR"
					}
				]
			},
			"system": "urn:oid:1.2.36.146.595.217.0.1",
			"value": "12345",
			"period": {
				"start": "2001-05-06"
			},
			"assigner": {
				"display": "Acme Healthcare"
			}
		}
	],
	"active": true,
	"name": [
		{
			"use": "official",
			"family": "Chalmers",
			"given": [
				"Peter",
				"James"
			]
		},
		{
			"use": "usual",
			"given": [
				"Jim"
			]
		},
		{
			"use": "maiden",
			"family": "Windsor",
			"given": [
				"Peter",
				"James"
			],
			"period": {
				"end": "2002"
			}
		}
	],
	"telecom": [
		{
			"use": "home"
		},
		{
			"system": "phone",
			"value": "(03) 5555 6473",
			"use": "work",
			"rank": 1
		},
		{
			"system": "phone",
			"value": "(03) 3410 5613",
			"use": "mobile",
			"rank": 2
		},
		{
			"system": "phone",
			"value": "(03) 5555 8834",
			"use": "old",
			"period": {
				"end": "2014"
			}
		}
	],
	"gender": "male",
	"birthDate": "1974-12-25",
	"_birthDate": {
		"extension": [
			{
				"url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime",
				"valueDateTime": "1974-12-25T14:35:45-05:00"
			}
		]
	},
	"deceasedBoolean": false,
	"address": [
		{
			"use": "home",
			"type": "both",
			"text": "534 Erewhon St\nPeasantVille, Rainbow, Vic3999",
			"line": [
				"534 Erewhon St"
			],
			"city": "PleasantVille",
			"district": "Rainbow",
			"state": "Vic",
			"postalCode": "3999",
			"period": {
				"start": "1974-12-25"
			}
		}
	],
	"contact": [
		{
			"relationship": [
				{
					"coding": [
						{
							"system": "http://terminology.hl7.org/CodeSystem/v2-0131",
							"code": "N"
						}
					]
				}
			],
			"name": {
				"family": "du Marché",
				"_family": {
					"extension": [
						{
							"url": "http://hl7.org/fhir/StructureDefinition/humanname-own-prefix",
							"valueString": "VV"
						}
					]
				},
				"given": [
					"Bénédicte"
				]
			},
			"additionalName": [
				{
					"use": "nickname",
					"given": [
						"Béné"
					]
				}
			],
			"telecom": [
				{
					"system": "phone",
					"value": "+33 (237) 998327"
				}
			],
			"address": {
				"use": "home",
				"type": "both",
				"line": [
					"534 Erewhon St"
				],
				"city": "PleasantVille",
				"district": "Rainbow",
				"state": "Vic",
				"postalCode": "3999",
				"period": {
					"start": "1974-12-25"
				}
			},
			"additionalAddress": [
				{
					"use": "work",
					"line": [
						"123 Smart St"
					],
					"city": "PleasantVille",
					"state": "Vic",
					"postalCode": "3999"
				}
			],
			"gender": "female",
			"period": {
				"start": "2012"
			}
		}
	],
	"managingOrganization": {
		"reference": "Organization/1"
	}
}

Vamos agora olhar para o objeto Root, que será nossa classe principal de paciente:

Vamos agora olhar para o objeto Root, que será nossa classe principal de paciente:

Class App.Model.Root Extends (%Persistent, %JSON.Adaptor)
{

Property resourceType As %String;
Property idJSON As %String(%JSONFIELDNAME = "id");
Property identifier As list Of App.Model.Root.Identifier;
Property active As %Boolean;
Property name As list Of App.Model.Root.Name;
Property telecom As list Of App.Model.Root.Telecom;
Property gender As %String;
Property birthDate As %String;
Property deceasedBoolean As %Boolean;
Property address As list Of App.Model.Root.Address;
Property contact As list Of App.Model.Root.Contact;
Property managingOrganization As App.Model.Root.ManagingOrganization;
}

Et voilà! Agora podemos armazenar nosso JSON como um objeto regular.

Próximos passos

Não é ruim poder gerar automaticamente as classes, mas seria interessante poder transformar automaticamente o JSON em uma instância das classes que criamos, levando em conta as particularidades dos nomes das propriedades.

Comentarios (0)1
Inicie sesión o regístrese para continuar
Artículo
· 3 hr atrás Lectura de 11 min

IKO Plus: HSSYS Mirroring with IKO

Enabling HSSYS Mirroring Out of the Gate with IKO

For those of us building InterSystems workloads on Kubernetes, we are definitely spoiled with the InterSystems Kubernetes Operator (IKO) doing the heavy lifting and mirroring on day one.  Where us spoiled brats jump up and down is when we try to add additional databases/namespaces when we provision from HealthConnect containers on day two, while others get to utilize HealthShare Mirroring for this task, the prerequisite of mirroring HSSYS out of the gate has been somewhat elusive.  Here is example on how you can this powerful feature up and running with the employment of IKO and IrisClusters.

HealthCare Mirroring

The documentation for this feature is great and highlights the functionally of what its protecting etc, but it misses bragging about the magical operational add it provides.  What it means for those automating workloads is that once you mirror HSSYS, any subsequent namspace provisioned after it gets automatically mirrored for free.

The documentation provides a process of which to set it up, which is manual in nature, that we need to automate to have IKO carry it out, which is great.

https://docs.intersystems.com/healthconnect20253/csp/docbook/Doc.View.cl...


The top three things that need to happen:

  • Mirror HSSYS
  • Schedule the Mirroring Agent
  • Use the Installer Wizard to create a Foundation namespace on primary

Enabling IKO Features

The bits and pieces of functionality we exploited to get this work.

  • iko seeding
  • iris-main --after operations

I wrote up seeding pretty good in a previous post:

https://community.intersystems.com/post/iko-plus-database-management-mig...

But something I like that we have been taking advantage of is the --before and --after flags in the iris-main `args`. 

          args:
          - --before
          - /usr/bin/bash /hs/before/before.sh
          - --after
          - /usr/bin/bash /hs/after/after.sh

These are configmaps that are mounted as scripts and execute as their label indicates.

before.sh - iris is not available here, good for grabbing stuff, oras operations possibly, file system stuff, whatever.

after.sh - iris is available here, run cos code, import ipm packages, spin sugar.
 

1️⃣ Mirror

Obviously mirror this, but also declare a databases block as mirrored using a seed from the container location of the database.

    data:
      irisDatabases:
      - directory: /irissys/data/IRIS/mgr/hssys
        mirrored: true
        name: HSSYS
        seed: /usr/irissys/mgr/hssys/
      mirrorMap: primary,backup
      mirrored: true

2️⃣ initContainer

Now, the upcoming yaml slingers will say this is a task for an agent or the operator itself, but is emulating the manual task for provisioning HSSYS as a mirror. 

Add a volume and a volumeMount for the shared volume for the init container ...

      volumeMounts:
      - mountPath: /hs/before/
        name: ikoplus-before-volume
      - mountPath: /hs/after/
        name: ikoplus-after-volume
      - name: hssys-volume
        mountPath: /hssys

...and the initContainer itself.

          initContainers:
            - name: hssys-copy
              image: alpine:3.19
              env:
                - name: POD_NAME
                  valueFrom:
                    fieldRef:
                      fieldPath: metadata.name
              command: ["/bin/sh", "-c"]
              args:
                - |
                  set -euo pipefail

                  # Install deps: tar + kubectl
                  apk add --no-cache tar curl ca-certificates >/dev/null
                  KUBECTL_VERSION="${KUBECTL_VERSION:-v1.29.0}"
                  curl -fsSL -o /usr/local/bin/kubectl "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
                  chmod +x /usr/local/bin/kubectl

                  if [ "$POD_NAME" = "{{ $.Release.Name }}-hssys-data-0-1" ]; then
                    echo "Hostname match; performing pod-to-pod hssys copy..."
                    kubectl cp -n {{ .Release.Namespace }} {{ $.Release.Name }}-hssys-data-0-0:/irissys/data/IRIS/mgr/hssys/IRIS.DAT /hssys/IRIS.DAT
                    echo "Copy complete."
                  else
                    echo "Hostname does not match; skipping copy."
                  fi
              securityContext:
                runAsUser: 0 # run as root
                runAsNonRoot: false
                readOnlyRootFilesystem: false
              volumeMounts:
              - name: hssys-volume
                mountPath: /hssys

Its quite simple and only runs the script in the initi container if its running on the backup at start.  This takes the mirrored version of hssys/IRIS.DAT and pops it on the backup in /hssys/IRIS.DAT. 

3️⃣ The after.sh party 🎉

Almost verbatim to the manual steps: 

  • Unmount
  • Copy
  • Mount
  • Activate
  • CatchUp
  • Schedule Task

... only do this once, and only do this on the backup at provision time.

--- 
apiVersion: v1
data:
  after.sh: |-
    #!/usr/bin/bash

    if ! [ -f "/irissys/data/after.done" ]; then
      echo "{{ $.Release.Name }} After Script..."
      if [[ "$(hostname)" == "{{ $.Release.Name }}-hssys-data-0-1" ]]; then
        echo "After for mirror b only..."
        iris session IRIS <<'EOF'
    zn "%SYS"
    w ##class(SYS.Database).%OpenId("/irissys/data/IRIS/mgr/hssys/").Dismount()
    Set sc = ##class(%File).CopyFile("/hssys/IRIS.DAT", "/irissys/data/IRIS/mgr/hssys/IRIS.DAT", 1)
    w ##class(SYS.Database).%OpenId("/irissys/data/IRIS/mgr/hssys/").Mount()
    SET sc = ##class(SYS.Mirror).ActivateMirroredDatabase("/irissys/data/IRIS/mgr/hssys/")
    Set db=##class(SYS.Database).%OpenId("/irissys/data/IRIS/mgr/hssys/")
    set SFNlist = $LISTBUILD(db.SFN)
    SET sc = ##class(SYS.Mirror).CatchupDB(SFNlist)
    Halt
    EOF
      fi

      iris session IRIS <<'EOF'
    zn "%SYS"
    set tSC=##Class(Security.Users).Create("ikoplus","%All","ikoplus","HSSYS","","","",0,1,,,,,,1,1)
    zn "HSLIB"
    do ##class(HS.Util.Mirror.Task).Schedule("HSSYS")
    Halt
    EOF

      touch "/irissys/data/after.done"
    fi

    exit 0

Note the service account is needed in the data pod to exec across IrisClusters, you will see that in the reference IrisCluster below.

Reference IrisCluster

Ill paste this here, but I ran it as chart so the Helm tags are resident, but adapt it to your own use or put on a pair of helm glasses.  The things you do not see here are the secrets, which are the container pull secrets/licenses, but the rest is there including the applicable service account with rootabega on the cluster to exec across pods.

 
iriscluster-hssys.yaml

I named the namespace after @Eduard Lebedyuk who afforded me some banter trying to figure this one out.

kubectl create ns eduard
helm install ikoplus14 .

Flow

So the dev story for the order of things is this:

IKO gets put into motion through the admission hook to create a mirrored HealthConnect deployment, which is a pair of IrisClusters and an arbiter.  The Primary comes up with the instruction to create HSSYS from a seed in the container, then a bit of ugliness occurs, but nobody sees it except us, or observability if they are pushing logs.

If you were to freeze frame at this moment, the state would look a little bit like:

 
Ugly

Its inside a teaser because well, its ugly, and the ugliness occurs on both primary and backup on creation.

However, after.sh backup comes up, it takes care of all of that, but copying over HSSYS, activating, catching things up and covers it up like an Epstein file in a public bucket.

Attestation

Now shell into your active primary, and create 5 databases using the installer.

For i=1:1:5 { Set ns = "OMOP"_i Do ##class(HS.Util.Installer.Foundation).Install(ns) }

Now sit back and relax, as the task fires every 5 minutes out of the box and the installer chugs a bit in the loop, inspecting on the backup member you should see the mirrored databases being created, activated, and caught up one by one, for free.

Addtionally, there is a ui to see how hard the mirroring agent is working and what its been up to.  Here you can see the last action was class mappings... if you have every watched the installer do its thing in the ui or from the backend, that is definitely the latest of the operations.

Comentarios (0)1
Inicie sesión o regístrese para continuar
Anuncio
· 12 hr atrás

ObjectScript 開発の未来を、共に形づくりませんか?

開発者の皆さん、こんにちは!

次世代の ObjectScript 開発ツールを形づくるための取り組みに、皆さまのご参加をお待ちしています。IRIS開発者向けに特別に設計された、AIを活用したObjectScript コーディングアシスタントの開発に取り組んでいます。

ObjectScript 向けに適応された汎用的な AI ではなく、ObjectScript のイディオム、IRIS API、interoperability  プロダクション、そして実際の開発者ワークフローを理解するためにゼロから構築されたソリューションです。

本当に役立つものを作るためには、皆さまのご意見をぜひお聞かせください。

👉 ObjectScript Coder Agent 開発者アンケート(英語) 👈

アンケートは約5分程度の内容で、次のような質問に回答いただきます。

  • あなたの役割と経験レベル
  • 現在の問題点とワークフロー
  • 最も重要な機能(コード生成、デバッグ、学習サポートなど)
  • このようなツールをどこでどのように使いたいか

この調査は、ObjectScript の経験レベルを問わず、あらゆる開発者の方が対象となります。

What’s next?

  • 集計結果はコミュニティと共有されます。
  • ご関心のある方には、早期ベータ版への招待を行う場合があります。
  • 開発の進捗に応じて、定期的に最新情報をお知らせします

皆さまからのフィードバックは、開発の優先順位や機能の方向性に反映されます。

ご不明な点などございましたら返信欄へのコメント、または thomas.dyar@intersystems.com まで直接ご連絡ください。

ObjectScript コミュニティのためのツールづくりにご協力いただき、ありがとうございます。皆様からのご意見をお待ちしております!

Comentarios (0)1
Inicie sesión o regístrese para continuar
InterSystems Official
· 22 hr atrás

Third InterSystems IRIS, InterSystems IRIS for Health, and HealthShare Health Connect 2026.1 Developer Preview available

The third developer previews of InterSystems IRIS® data platform, InterSystems IRIS® for Health, and HealthShare® Health Connect 2026.1 have been posted to the WRC developer preview site.  Containers can be found on our container registry and are tagged latest-preview.

These developer previews includes the dropping of Mac Intel support starting from 2026.1.0, and the adding back of Windows Server 2019 support to 2026.1.0.

Initial documentation can be found at these links below:

The documentation links currently serve as placeholders and will be updated as we get closer to the final build and have more details to share. Here are some notable additions to the 2026.1 documentation:

Because this is an early Developer Preview, some content is still in progress, and we’ll provide full documentation as features are finalized. We appreciate your patience and feedback during this phase!

Availability and Package Information

This release comes with classic installation packages for all supported platforms.  For a complete list, refer to the Supported Platforms document.

Installation packages and preview keys are available from the WRC's preview download site or through the evaluation services website (tick the "Show Preview Software" box).

Comentarios (0)1
Inicie sesión o regístrese para continuar
Pregunta
· 23 hr atrás

Sending HTTP Request as an application/x-www-form-urlencoded

In my previous HttpRequests, I have used Content-Type = application/json but I have a vendor that is requesting we send application/x-www-form-urlencoded. How would I go about formatting the Payload as x-www-form-urlencoded?

Thanks

2 nuevos comentarios
Comentarios (2)3
Inicie sesión o regístrese para continuar