Serialization

Any OWLClass you get back from the library can be serialized to JSON, JSON-LD, OWL/XML, or Markdown with a one-line method call. On top of that, the FOLIO client has a format_classes_for_llm() helper that emits token-efficient JSONL for use in prompt construction — particularly handy when you want to hand a list of candidate classes to an LLM for classification, ranking, or retrieval-augmented generation.

Format overview

MethodReturnsBest forNotes
to_json()strSmallest representation; round-trips via from_json()Thin wrapper around Pydantic’s model_dump_json()
to_jsonld()dictInterop with JSON-LD consumers, prompt context with IRIsReturns a dict, not a string. The @context includes a None / "null" key from the lxml namespace map — see the caveat below
to_owl_xml()strWriting a finished OWL/XML fragment to a file or APIPretty-printed, UTF-8. Round-trippable to lxml via to_owl_element()
to_owl_element()lxml.etree.ElementBuilding a larger OWL document programmatically with lxmlLower-level than to_owl_xml(); used internally by it
to_markdown()strHuman-readable summaries for READMEs, chat messages, LLM promptsHas section headers (## Labels, ## Definition, ## Sub Class Of, …)

All of the above are methods on OWLClass. OWLObjectProperty does not have any of them — see the dedicated section below.

JSON: to_json() and from_json()

to_json() returns a single-line JSON string produced by Pydantic’s model_dump_json(). It includes every field of the class (nulls and empty collections and all), which makes it the faithful round-trip format.

from folio import FOLIO
from folio.models import OWLClass

f = FOLIO()
mi = f["R8BD30978Ccbc4C2f0f8459f"]  # Michigan

print(mi.to_json())

# Output:
# {"iri":"https://folio.openlegalstandard.org/R8BD30978Ccbc4C2f0f8459f","label":"Michigan","sub_class_of":["https://folio.openlegalstandard.org/R1E70ce4D699e90144cB32b8"],"parent_class_of":[],"is_defined_by":null,"see_also":[],"comment":null,"deprecated":false,"preferred_label":"Michigan","alternative_labels":["US+MI"],"translations":{},"hidden_label":"US+MI","definition":null,"examples":[],"notes":[],"history_note":null,"editorial_note":null,"in_scheme":null,"identifier":"NAM-US-US+MI","description":null,"source":null,"country":null}

To reconstruct the class from its JSON form, use the OWLClass.from_json() classmethod. It’s a thin wrapper around Pydantic’s model_validate_json():

json_str = mi.to_json()
mi_roundtrip = OWLClass.from_json(json_str)

print(mi == mi_roundtrip)
print(mi_roundtrip.label, mi_roundtrip.iri)

# Output:
# True
# Michigan https://folio.openlegalstandard.org/R8BD30978Ccbc4C2f0f8459f

to_json() produces a single line because that’s what model_dump_json() does by default. For pretty-printed output, run it back through jsonto_json() itself does not accept an indent argument:

import json

pretty = json.dumps(json.loads(mi.to_json()), indent=2)
print(pretty)

# Output:
# {
#   "iri": "https://folio.openlegalstandard.org/R8BD30978Ccbc4C2f0f8459f",
#   "label": "Michigan",
#   "sub_class_of": [
#     "https://folio.openlegalstandard.org/R1E70ce4D699e90144cB32b8"
#   ],
#   "parent_class_of": [],
#   "is_defined_by": null,
#   "see_also": [],
#   "comment": null,
#   "deprecated": false,
#   "preferred_label": "Michigan",
#   "alternative_labels": ["US+MI"],
#   ...
# }

JSON-LD: to_jsonld()

to_jsonld() emits a JSON-LD representation with a FOLIO @context, @id, @type: "owl:Class", and all populated SKOS/DC/RDFS fields mapped to their prefixed predicates.

Important: to_jsonld() returns a dict, not a str. If you want a string, wrap the call in json.dumps(...). The method’s docstring claims “JSON-LD string,” but the actual implementation returns the Python dict it built internally.

import json

mi = f["R8BD30978Ccbc4C2f0f8459f"]  # Michigan
jsonld = mi.to_jsonld()

print(type(jsonld).__name__)
# Output:
# dict

print(json.dumps(jsonld, indent=2))

# Output:
# {
#   "@context": {
#     "null": "https://folio.openlegalstandard.org/",
#     "dc": "http://purl.org/dc/elements/1.1/",
#     "v1": "http://www.loc.gov/mads/rdf/v1#",
#     "owl": "http://www.w3.org/2002/07/owl#",
#     "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
#     "xsd": "http://www.w3.org/2001/XMLSchema#",
#     "folio": "https://folio.openlegalstandard.org/",
#     "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
#     "skos": "http://www.w3.org/2004/02/skos/core#",
#     "xml": "http://www.w3.org/XML/1998/namespace"
#   },
#   "@id": "https://folio.openlegalstandard.org/R8BD30978Ccbc4C2f0f8459f",
#   "@type": "owl:Class",
#   "rdfs:label": "Michigan",
#   "skos:prefLabel": "Michigan",
#   "skos:altLabel": ["US+MI"],
#   "rdfs:subClassOf": [
#     {"@id": "https://folio.openlegalstandard.org/R1E70ce4D699e90144cB32b8"}
#   ],
#   "skos:hiddenLabel": "US+MI",
#   "dc:identifier": "NAM-US-US+MI"
# }

The @context caveat. The @context dict copies the lxml namespace map verbatim, and lxml uses the Python value None as the key for the default namespace. Pydantic/json can’t render a literal None key as a JSON string, so when you call json.dumps() on the result, Python coerces it to the literal string "null" — which is what you see in the output above. In the Python dict (pre-json.dumps) the key is an actual None:

print(list(jsonld["@context"].keys())[:3])

# Output:
# [None, 'dc', 'v1']

If you need strictly-valid JSON-LD (many validators reject a "null" context key), post-process the @context to either remove it or replace it with @vocab before serializing:

ctx = {k: v for k, v in jsonld["@context"].items() if k is not None}
ctx["@vocab"] = "https://folio.openlegalstandard.org/"
jsonld["@context"] = ctx

For LLM prompt construction and internal interop, the raw dict is almost always fine as-is — just be aware of the quirk before wiring it into a conformance-checking pipeline.

OWL XML: to_owl_xml() and to_owl_element()

to_owl_xml() returns a pretty-printed, UTF-8-decoded OWL/XML fragment for the class. It round-trips to lxml via to_owl_element(), which is the lower-level method that returns the underlying lxml.etree.Element before it’s stringified.

mi = f["R8BD30978Ccbc4C2f0f8459f"]  # Michigan
print(mi.to_owl_xml())

# Output:
# <owl:Class xmlns="https://folio.openlegalstandard.org/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:v1="http://www.loc.gov/mads/rdf/v1#" xmlns:owl="http://www.w3.org/2002/07/owl#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xsd="http://www.w3.org/2001/XMLSchema#" xmlns:folio="https://folio.openlegalstandard.org/" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:skos="http://www.w3.org/2004/02/skos/core#" rdf:about="https://folio.openlegalstandard.org/R8BD30978Ccbc4C2f0f8459f">
#   <rdfs:subClassOf rdf:resource="https://folio.openlegalstandard.org/R1E70ce4D699e90144cB32b8"/>
#   <rdfs:label>Michigan</rdfs:label>
#   <skos:altLabel>US+MI</skos:altLabel>
#   <skos:hiddenLabel>US+MI</skos:hiddenLabel>
#   <skos:prefLabel>Michigan</skos:prefLabel>
# </owl:Class>

When to use to_owl_element() vs to_owl_xml(). Use to_owl_element() when you’re composing a larger OWL document with lxml — appending many class elements to a parent rdf:RDF element, for example. Use to_owl_xml() when you want a finished string to write to disk, POST over HTTP, or drop into a log:

import lxml.etree

NSMAP = {
    "owl": "http://www.w3.org/2002/07/owl#",
    "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
    "skos": "http://www.w3.org/2004/02/skos/core#",
}

root = lxml.etree.Element(f"{{{NSMAP['rdf']}}}RDF", nsmap=NSMAP)
for iri in ["R8BD30978Ccbc4C2f0f8459f", "R8372b3AC7127F95f5238a85"]:
    root.append(f[iri].to_owl_element())

xml_bytes = lxml.etree.tostring(root, pretty_print=True, encoding="utf-8")
# xml_bytes now contains a small OWL document with two <owl:Class> entries.

No Turtle / N-Triples. OWLClass has no to_owl_ttl(), to_turtle(), to_nt(), or any other RDF-serialization method. OWL/XML is the only RDF-flavored output the library produces directly. If you need Turtle, pipe to_owl_xml() through rdflib externally:

import rdflib
g = rdflib.Graph()
g.parse(data=mi.to_owl_xml(), format="xml")
ttl = g.serialize(format="turtle")

Markdown: to_markdown()

to_markdown() returns a human-readable Markdown summary of the class with sections for Labels, Definition, Examples, Sub Class Of, Parent Class Of, See Also, Notes, and all of the Dublin Core / MADS fields. Everything empty is omitted.

mi = f["R8BD30978Ccbc4C2f0f8459f"]  # Michigan
print(mi.to_markdown())

# Output:
# # Michigan
#
# **IRI:** https://folio.openlegalstandard.org/R8BD30978Ccbc4C2f0f8459f
#
# ## Labels
#
# **Preferred Label:** Michigan
#
# **Alternative Labels:**
#
# - US+MI
#
# **Hidden Label:** US+MI
#
# ## Sub Class Of
#
# - https://folio.openlegalstandard.org/R1E70ce4D699e90144cB32b8
#
# **Deprecated:** False
#
# **Identifier:** NAM-US-US+MI

The typical use cases are dropping a class summary into a README, posting it as a message to a chat channel, or including it in an LLM prompt where the section headers help the model keep track of what each field means. For LLM prompts where you want structured-but-compact output for many classes, prefer the JSONL helper below.

JSONL for LLM prompts: format_classes_for_llm()

format_classes_for_llm() lives on the top-level FOLIO client (not on OWLClass) and produces one JSON object per line — JSONL — suitable for dropping into LLM prompts. Each object contains only the non-empty fields of the class, keeping token usage low:

  • iri
  • label
  • preferred_label
  • definition
  • alt_labels
  • parents — resolved to the labels (or preferred labels) of each parent IRI, not the raw IRIs

Signature:

FOLIO.format_classes_for_llm(owl_classes: List[OWLClass]) -> str

Example — feed the top 3 prefix-search results for "Mich" into the formatter:

results = f.search_by_prefix("Mich")[:3]
for c in results:
    print(c.label)
# Output:
# Michigan
# Michoacan de Ocampo
# Michigan State Courts

jsonl = f.format_classes_for_llm(results)
print(jsonl)

# Output:
# {"iri": "https://folio.openlegalstandard.org/R8BD30978Ccbc4C2f0f8459f", "label": "Michigan", "preferred_label": "Michigan", "alt_labels": ["US+MI"], "parents": ["United States of America (Location)"]}
# {"iri": "https://folio.openlegalstandard.org/R9F9550531bbb547c4724478", "label": "Michoacan de Ocampo", "preferred_label": "Michoacan de Ocampo", "alt_labels": ["MX+MIC"], "parents": ["Mexico"]}
# {"iri": "https://folio.openlegalstandard.org/RA3C143EB06fcBA8f410Fd50", "label": "Michigan State Courts", "preferred_label": "Michigan State Courts", "definition": "Michigan State Courts are the judicial system of the state of Michigan, consisting of the Michigan Supreme Court, Michigan Circuit Court, Michigan District Court, and Michigan Court of Appeals.", "alt_labels": ["MI"], "parents": ["State Courts"]}

Notice how each line only contains the fields that have values for that class — the leaf Michigan entry has no definition, the Michigan State Courts entry does, and nothing is padded with nulls or empty lists. That per-class pruning is what makes this format token-efficient for search_by_llm() and similar workflows where you’re passing tens or hundreds of candidate classes into a single prompt.

This is the exact formatter search_by_llm() uses internally when building its prompt. See LLM Integration for the downstream workflow.

Validation: is_valid()

Both OWLClass and OWLObjectProperty expose an is_valid() method that returns True if self.label is not None. It’s a cheap sanity check for synthetic or partially-constructed instances — most notably the owl:Thing sentinel that get_parents() appends to the end of every parent chain, which has label=None.

from folio.models import OWLClass

print(f["R8BD30978Ccbc4C2f0f8459f"].is_valid())
# Output:
# True

empty = OWLClass(iri="https://example.com/x")
print(empty.is_valid())
# Output:
# False

Use it to guard a rendering loop:

for parent in f.get_parents("https://folio.openlegalstandard.org/R8BD30978Ccbc4C2f0f8459f"):
    if not parent.is_valid():
        continue  # skips the owl:Thing sentinel
    print(parent.to_markdown())

Serializing OWLObjectProperty

OWLObjectProperty has no to_owl_xml(), to_markdown(), to_json(), or to_jsonld() methods. Only is_valid() and __str__() are defined. This is a deliberate asymmetry with OWLClass — object properties are a much smaller, simpler schema, so the library doesn’t bother to hand-roll formatters for them.

To serialize an object property, reach for Pydantic directly. Every OWLObjectProperty is a BaseModel, so model_dump_json() and model_dump() work out of the box:

prop = f.get_property("https://folio.openlegalstandard.org/R0q5hTo2yTMlnIAbmFnwCH")
print(prop.model_dump_json(indent=2))

# Output:
# {
#   "iri": "https://folio.openlegalstandard.org/R0q5hTo2yTMlnIAbmFnwCH",
#   "label": "hasFigure",
#   "sub_property_of": [
#     "https://folio.openlegalstandard.org/RCBqIJm4IPngJgyvh49kP62"
#   ],
#   "domain": [],
#   "range": [],
#   "inverse_of": null,
#   "preferred_label": null,
#   "alternative_labels": [],
#   "definition": null,
#   "examples": []
# }

If you want JSON-LD, OWL/XML, or Markdown for a property, you’ll need to build it yourself — there are no helpers. In practice, model_dump_json() is enough for logging, caching, and passing properties into LLM prompts, which is why no one has needed the rest yet.

See also

For building structured prompts from FOLIO classes, see LLM Integration — it covers search_by_llm and parallel_search_by_llm, both of which use format_classes_for_llm() under the hood. For the underlying object-property graph and raw triples, see Properties & Relationships. For the complete method catalog on OWLClass, OWLObjectProperty, and the FOLIO client, see the API Reference.