minecraft-schemas 0.4.1.post1
Parse Minecraft-relative JSON to structured objects
Requires Python: >=3.12
Keywords: minecraft, schema

# minecraft-schemas

A Python library for help you to parse Minecraft-relative JSON to structured objects.

**Disclaimer:** Although the project name contains "minecraft", this project is not supported by Mojang Studios or Microsoft.

## Notice for users/developers migrated from `minecraft-schemes`

This project is renamed from [`minecraft-schemes`](https://pypi.org/project/minecraft-schemes), with a full package structure reorganization.

For more information about migration, see the [version history file](HISTORY.md) for more details.
(Or scroll down to see the version history if you are reading this on PyPI.)

Due to limited time and energy, I am unable to provide a complete backward compatibility solution for this release. Sorry for the inconvenience.

## Features

### Already implemented

- Easy installing
- Open source
- All public APIs are static typed
- Supports parsing various file structures used by Mojang and Minecraft ([see below](#supported-file-structures))
- Easy-to-use file structure definitions, powered by [`attrs`](https://www.attrs.org)
- Rapidly file parsing, powered by [`cattrs`](https://catt.rs)
- Conditional testing for game/command line options and dependency libraries (in `client.json`)

### Not implemented yet (not a complete list)

- [ ] Parse/build supports for `launcher_profiles.json` (used by official Minecraft Launcher)
- [ ] Game/JVM command line options concatenating and completing

## Supported file structures

### <span id="file-structure-version-manifest"></span>`version_manifest.json` and `version_manifest_v2.json`

**See more:**
[Minecraft Wiki](https://minecraft.wiki/w/Version_manifest.json)

- A JSON file that list Minecraft versions available in the official launcher.

### <span id="file-structure-client-manifest"></span>`client.json`

**See more:**
[Minecraft Wiki](https://minecraft.wiki/w/Client.json)

- A JSON file that accompanies client.jar in `.minecraft/versions/<version>` and lists the version's attributes.
- Usually named `<game version>.json`.
- Don't confuse this file with `version.json`; they are fundamentally different.

### <span id="file-structure-asset-index"></span>Asset index file

**See more:**
[Minecraft Wiki (only Chinese version)](https://zh.minecraft.wiki/w/%E6%95%A3%E5%88%97%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6#%E8%B5%84%E6%BA%90%E7%B4%A2%E5%BC%95)

- A series of JSON files used to query the hash value of the corresponding hashed resource file based on the resource path, in order to invoke
  the file.
- Can be downloaded from the URL pointed in the `client.json`: `[Root Tag] > "assetIndex" > "url"`

### <span id="file-structure-version-attributes"></span>`version.json`

**See more:**
[Minecraft Wiki](https://minecraft.wiki/w/Version.json)

- A JSON file that offers some basic information about the version's attributes.
- Embedded within client.jar in `.minecraft/versions/<version>` and `server.jar`.
- Don't confuse this file with `client.json`; they are fundamentally different.

### <span id="file-structure-mojang-java-index-manifest"></span>Mojang Java Runtime index file and manifest files

- A JSON file that list manifest files of Java Runtime provided by Mojang via their "codename".
- Not documented by Minecraft Wiki or Mojang, but it is believed to be for the purposes described above.

### <span id="file-structure-yggdrasil-api-responses"></span>Yggdrasil API Responses

**See more:**
[Unofficial Yggdrasil server technical specification, provided by
`authlib-injector` (only Chinese version)](https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83)

Included the following things:

- Error response
- Endpoint `/authenticate` response and its parts
- Endpoint `/refresh` response and its parts
- `authlib-injector`-compatible Yggdrasil API metadata
- `authlib-injector`-compatible Yggdrasil Server metadata
    - Included in the `authlib-injector`-compatible Yggdrasil API metadata: `[Root Tag] > "meta"` (JSON) or `api_metadata.serverMetadata` (parsed)
    - According to this [
      `authlib-injector` Wiki](https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#meta-%E4%B8%AD%E7%9A%84%E5%85%83%E6%95%B0%E6%8D%AE)
      section about the server metadata, this structure is not mandatory. Regardless of whether the parsing is successful or not, users/developers
      should access and manipulate it as a regular dict.
- Player texture property
    - As a part of Yggdrasil API endpoint `/authenticate` and `/refresh`.
    - Usually encoded in Base64. Users/developers should decode and load it manually.

## Install

Install `minecraft-schemas` using pip:

```commandline
pip install minecraft-schemas
```

The release page also provides various versions of wheel files for manual download and installation.

## API Documentation

`mcschemas`'s most useful functionalities are the following APIs:

- `mcschemas.Schemas`
    - An enum class that declares currently supported schemas. All schemas have parsing support, but no one currently have build support.
- `mcschemas.parse(obj, schema, /, *, converter=None)`
    - Parse `obj` as the given `schema` to the corresponding type.
    - `schema` must be a member of enum `mcschemas.Schemas`.
    - `converter` can be an instance of `cattrs.BaseConverter` or its subclasses.
        - Default is `None`. At this time, a `mcschemas.DedicatedConverter` instance will be automatically created for internal
          structuring.
- `mcschemas.loads(s, schema, /, *, converter=None, **json_loads_kwargs)`
    - Deserialize the string `s`, then parse the deserialized result as the given `schema` to the corresponding type.
    - `schema` and `converter` is identical to `mcschemas.parse()`.
    - `**json_loads_kwargs` will be passed to `json.loads()`, except the keyword `s`.
- `mcschemas.load(fp, schema, /, *, converter=None, **json_load_kwargs)`
    - Identical to `mcschemas.loads()`, but instead of a string, deserialize the file-like object `fp`, then parse the deserialized result as the
      given `schema` to the corresponding type.
        - `fp` must be a `.read()`-supporting text file.
    - `schema` and `converter` is identical to `mcschemas.parse()`.
    - `**json_load_kwargs` will be passed to `json.load()`, except the keyword `fp`.
- `mcschemas.loadVersionAttrsFromClientJar(file, /, *, converter=None, **json_load_kwargs)`
    - A convenience function for loading, deserializing and parsing `version.json` from `client.jar`.
    - The schema is fixed to `mcschemas.Schemas.VERSION_ATTRIBUTES`.
    - `converter` is identical to `mcschemas.parse()`.
    - `**json_load_kwargs` is identical to `mcschemas.load()`.
- _class_ `mcschemas.DedicatedConverter(*, regex_flags=0, detailed_validation=True, forbid_extra_keys=False)`
    - A converter for converting between structured and unstructured data according to the data structures defined in this package.
    - _classmethod_ `mcschemas.DedicatedConverter.configure(converter, /, *, regex_flags=0)`
        - Configure an existing converter to convert some specific types.
            - `converter` must be an instance of `cattrs.BaseConverter` or its subclasses.
    - Full documentation can be found in the code: [src/mcschemas/parser/converters.py](src/mcschemas/parser/converters.py)

And file structure model declarations:

|            Sub Package             |                                                                   `mcschemas.Schemas` Enum Member Name                                                                    |                                  Corresponding File Structure                                   |
|:----------------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------:|
| `mcschemas.models.versionmanifest` |                                                                            `VERSION_MANIFEST`                                                                             |   [`version_manifest.json` and `version_manifest_v2.json`](#file-structure-version-manifest)    |
| `mcschemas.models.clientmanifest`  |                                                                             `CLIENT_MANIFEST`                                                                             |                        [`client.json`](#file-structure-client-manifest)                         |
|   `mcschemas.models.assetindex`    |                                                                               `ASSET_INDEX`                                                                               |                         [Asset index file](#file-structure-asset-index)                         |
|  `mcschemas.models.versionattrs`   |                                                                           `VERSION_ATTRIBUTES`                                                                            |                      [`version.json`](#file-structure-version-attributes)                       |
|   `mcschemas.models.mojangjava`    |                                                      `MOJANG_JAVA_RUNTIME_INDEX`<br/>`MOJANG_JAVA_RUNTIME_MANIFEST`                                                       | [Mojang Java Runtime index file and manifest files](#file-structure-mojang-java-index-manifest) |
|    `mcschemas.models.yggdrasil`    | `TEXTURE_PROPERTY`<br/>`ERROR_RESPONSE`<br/>`ENDPOINT_AUTHENTICATE_RESPONSE`<br/>`ENDPOINT_REFRESH_RESPONSE`<br/>`YGGDRASIL_API_METADATA`<br/>`REFERENCE_SERVER_METADATA` |               [Yggdrasil API Responses](#file-structure-yggdrasil-api-responses)                |

## Usage Example

### Parse `version_manifest.json`

Download [at here](https://piston-meta.mojang.com/mc/game/version_manifest_v2.json).

```python
import mcschemas
from mcschemas.models.enums import VersionType

with open('version_manifest.json', mode='r') as f:
    version_manifest = mcschemas.load(f, mcschemas.Schema.VERSION_MANIFEST)

print('Latest release:', version_manifest.latest.release)
print('Latest snapshot:', version_manifest.latest.snapshot)
print('Number of available versions:', len(version_manifest.versions))
print('Show information on the first 5 release versions:')
for idx, entity in enumerate(version_manifest.filterVersions(type=VersionType.RELEASE)):
    if idx >= 5:
        break
    print('  The ID of the release version (at index {0}):'.format(version_manifest.index(entity)), entity.id)
    print('  The release time of the release version (at index {0}):'.format(version_manifest.index(entity)), entity.releaseTime)
    print('  The last update time of the release version (at index {0}):'.format(version_manifest.index(entity)), entity.time)
```

### Parse `client.json`

This example code uses `client.json` from Minecraft Java Edition
1.21.11, download [at here](https://piston-meta.mojang.com/v1/packages/3f42d3ea921915b36c581a435ed03683a7023fb1/1.21.11.json).

```python
import mcschemas

with open('1.21.11.json', mode='r') as f:
    client_manifest_1_21_11 = mcschemas.load(f, mcschemas.Schema.CLIENT_MANIFEST)

print('Version ID:', client_manifest_1_21_11.id)
# The following field is structured as a member of enum mcschemas.enums.VersionType
print('Version Type:', str(client_manifest_1_21_11.type))
print('Asset version ID:', client_manifest_1_21_11.assetIndex.id)
print('Main class:', client_manifest_1_21_11.mainClass)
print('Release time:', client_manifest_1_21_11.releaseTime)
print('Last update time:', client_manifest_1_21_11.time)
print('Number of dependency libraries:', len(client_manifest_1_21_11.libraries))
client_jar_file_info = client_manifest_1_21_11.downloads.get('client')
if client_jar_file_info:
    print('URL to download the client JAR file:', client_jar_file_info.url)
```

### Parse asset index file

This example code uses the asset index file version 29. You can download
it [at here](https://piston-meta.mojang.com/v1/packages/aaf4be9d6e197c384a09b1d9c631c6900d1f077c/29.json).

```python
from pathlib import Path

import mcschemas

with open('29.json', mode='r') as f:
    asset_index = mcschemas.load(f, mcschemas.Schema.ASSET_INDEX)

print('Number of asset files:', len(asset_index.objects))
asset_file_relative_path = Path('icons/icon_256x256.png')
if asset_file_relative_path in asset_index.objects:
    target_asset_file_info = asset_index.objects[asset_file_relative_path]
    print('Information about asset file {0}: hash={1.hash}, size={1.size}'.format(asset_file_relative_path, target_asset_file_info))
```

### Parse `version.json` from a client JAR file

This example code uses the client JAR file from Minecraft Java Edition 1.21.11. You can download it in official Minecraft Launcher
or [at here](https://piston-data.mojang.com/v1/objects/ba2df812c2d12e0219c489c4cd9a5e1f0760f5bd/client.jar).

```python
from pathlib import Path

import mcschemas

version_attrs = mcschemas.loadVersionAttrsFromClientJar(Path.home().joinpath('.minecraft/versions/1.21.11/1.21.11.jar'))

print('Unique identifier of this client JAR:', version_attrs.id)
print('User-friendly name of this client JAR:', version_attrs.name)
print('Data version of this client JAR:', version_attrs.world_version)
print('Protocol version of this client JAR:', version_attrs.protocol_version)
print('Build time of this client JAR:', version_attrs.build_time)
if version_attrs.series_id:
    print('Series ID (branch name) of this client JAR:', version_attrs.series_id)
```

### Load `client.json`, then filter and concatenate command line

This example code uses `client.json` from Minecraft Java Edition
1.21.11, download [at here](https://piston-meta.mojang.com/v1/packages/3f42d3ea921915b36c581a435ed03683a7023fb1/1.21.11.json).

**Note:** this example only demonstrates basic conditional filtering and concatenation operations, and does not consider the replacement of
placeholder parameters (which may be supported in future versions).

```python
import mcschemas
from mcschemas.tools import rules

with open('1.21.11.json', mode='r') as f:
    client_manifest_1_21_11 = mcschemas.load(f, mcschemas.Schema.CLIENT_MANIFEST)

features: dict[str, bool] = {
    'is_demo_user'         : True,
    'has_custom_resolution': True
}
cmdline: list[str] = ['java']
for jvm_arg_entry in client_manifest_1_21_11.arguments.jvm:
    if rules.isArgumentCanBeAppended(jvm_arg_entry, features=features):
        cmdline.extend(jvm_arg_entry.value)
cmdline.append(client_manifest_1_21_11.mainClass)
for game_arg_entry in client_manifest_1_21_11.arguments.game:
    if rules.isArgumentCanBeAppended(game_arg_entry, features=features):
        cmdline.extend(game_arg_entry.value)
print('Concatenated command line (without placeholder replacements):', cmdline)
```

### Fetch API metadata from an `authlib-injector` compatible Yggdrasil Service

This example code requires [`httpx`](https://pypi.org/project/httpx). You can install it through `pip`.

The Yggdrasil service is provided by [Drasl](https://drasl.unmojang.org).

```python
import sys

import cattrs
import httpx

import mcschemas

try:
    resp = httpx.get('https://drasl.unmojang.org/authlib-injector').raise_for_status()
except httpx.HTTPError as exc:
    print('Failed to fetch the Yggdrasil API metadata: {0}'.format(exc))
    sys.exit(1)

# Parse the structure that already unserialized by `resp.json()`
api_metadata = mcschemas.parse(resp.json(), mcschemas.Schema.YGGDRASIL_API_METADATA)
# Or directly load from original response text
api_metadata = mcschemas.loads(resp.text, mcschemas.Schema.YGGDRASIL_API_METADATA)

# Parse the server metadata
# If failed to parse, we can still access/manipulate it as a regular dict
try:
    server_metadata = mcschemas.parse(api_metadata.serverMetadata, mcschemas.Schema.REFERENCE_SERVER_METADATA)
except cattrs.ClassValidationError:
    server_metadata = api_metadata.serverMetadata

print('Skin domain allowlist:')
for allowed_skin_domain in api_metadata.skinDomainAllowlist:
    print('  -', allowed_skin_domain)
print('Player profile signature public key (in PEM format):')
print(api_metadata.signaturePublicKey)
print('Yggdrasil server name (if exists):', server_metadata.get('serverName', ''))
print(
        'Yggdrasil server implementation name and version (if exists):',
        server_metadata.get('implementationName'),
        server_metadata.get('implementationVersion'),
)
print('Yggdrasil server supports non-email account name:', server_metadata.get('feature.non_email_login', False))
```

# History

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased] - TBD

## [0.4.1.post1] - 2026-02-21

### Removed

- **Parsing:** `mcschemas.parser.createConverter()` has been removed; it had previously been declared deprecated.
    - This function was removed at the code level in the previous release, but this was not mentioned in the previous release's changelog. Therefore,
      this release is specifically for correcting this oversight.

## [0.4.1] - 2026-02-21

### Added

- **Schema:** Supported to parse the response format of Yggdrasil APIs compatible with authlib-injector.
    - Corresponding model declarations at `mcschemas.models.yggdrasil`.
- **Schema:** Added the method `filterVersions()` for `mcschemas.models.versionmanifest.VersionManifest`
  to help filtering version entries by version type/ID.

### Changes

- **Project metadata:** Overhauled and updated the README.
- **Schema:** Registered `mcschemas.models.versionmanifest.VersionManifest` as a `collections.abc.MutableSequence` by implementing required abstract
  methods. In short, `mcschemas.models.versionmanifest.VersionManifest` now is a mutable sequence.
    - Its `__getitem__` method behaves a little differently from a regular sequence: when a string is passed in, it iterates through the internal list
      of version entries, searches for and returns the entry whose version ID **exactly matches** the string.
- **Schema:** Registered `mcschemas.models.assetindex.AssetIndex` as a `collections.abc.MutableMapping` by implementing required abstract methods.
  In short, `mcschemas.models.assetindex.AssetIndex` now is a mutable mapping.
    - Its `__getitem__`, `_setitem__` and `__delitem__` method behaves a little differently from a regular mapping: when a string is passed as the
      first argument after the `self`, it is first converted into a`pathlib.Path` instance before being passed to the internal asset objects mapping.

## [0.4.0] - 2026-02-09

This release focuses on renaming package and reorganizing the package internal structure.

Due to limited time and energy, I am unable to design a complete backward compatibility solution for this release. Sorry for the inconvenience.

### Backwards-incompatible Changes

- **Organizational:** The package is renamed to `mcschemas` (formally `mcschemes`),
  and this project is renamed to `minecraft-schemas` (formally `minecraft-schemes`).
    - Since the word "scheme" [[Merriam-Webster]](https://www.merriam-webster.com/dictionary/scheme) does not accurately reflect the content and
      objectives of this project, using "schema" [[Merriam-Webster]](https://www.merriam-webster.com/dictionary/schema) is clearly more appropriate.
- **Organizational:** Package structure changed:
    - All sub packages and modules including schema declarations are moved to `mcschemas.models`, detailed below:
        - `mcschemes.assetindex` -> `mcschemas.models.assetindex`
        - `mcschemes.clientmanifest` -> `mcschemas.models.clientmanifest`
        - `mcschemes.mojangjava` -> `mcschemas.models.mojangjava`
        - `mcschemes.versionattrs` -> `mcschemas.models.versionattrs`
        - `mcschemes.versionmanifest` -> `mcschemas.models.versionmanifest`
        - `mcschemes.enums` -> `mcschemas.models.enums`
        - `mcschemes.specials` -> `mcschemas.models.specials`
    - Parser submodule `mcschemes.tools.parser` is moved to `mcschemas.parser`.

### Changes

- **Project metadata:** Fixed typos in this version history file and README: `scheme` -> `schema`.

## [0.3.0.post1] - 2026-02-09

**Deprecated:**

- Announced the abandonment of the old package name `mcschemes` and the old project name `minecraft-schemes`.
    - The obsolete parts are kept in a separate branch `0.3.0-announced-deprecation`.

## [0.3.0] - 2026-01-31

### Added

- **Schema:** Added support for parsing version information files (the `version.json` embedded within `client.jar`).
- **Schema:** Added parse support for index file of Mojang Java Runtimes, and file manifest of each java runtime.
- **Parsing:** Added a dedicated converter class in `mcschemes.tools.parser.Converter.DedicatedConverter`.
    - This is intended to replace the `mcschemes.tools.parser.createConverter()`.

### Backwards-incompatible Changes

- **Schema:** `mcschemes.assetindex.AssetIndex` now use the `Path` object from the standard library's `pathlib` module to represent file relative
  paths in asset index files.

    1. Previously, it will use `str` to represent file relative paths, so you can access information (e.g. hash, size) by the following way:

        ```python
        from mcschemes.models.assetindex import AssetIndex
  
        asset_index: AssetIndex = ...  # Some operations to obtain the json and structure it to the AssetIndex instance
        file_info = asset_index.objects['icons/icon_128x128.png']
  
        [...]  # Do your operations for file_info
        ```

    2. Now, you need to use a `pathlib.Path` object as the key to access the corresponding information:

        ```python
        from pathlib import Path
        from mcschemes.models.assetindex import AssetIndex
      
        asset_index: AssetIndex = ...  # Some operations to obtain the json and structure it to the AssetIndex instance
        file_info = asset_index.objects[Path('icons/icon_128x128.png')]
      
        [...]  # Do your operations for file_info
        ```

### Deprecations

- **Parsing:** `mcschemes.tools.parser.createConverter()` is now marked as deprecated and will be removed in future versions.
    - Now pass a converter class based on `cattrs.Converter` to the kw-only argument ``converter_class`` is no longer determines the type of the
      returned dedicated converter instance.

### Changes

- **Project metadata:** This version history file has been revised to conform to the format described
  in [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- **Project metadata:** Fully updated the [README file](README.md):
    - Added a summary of the main features and benefits.
    - Added a summary of file structures supported by this library.
    - Usage example are now more useful and better represent typical use cases.
- **Organizational:** Reorganized the project structure:
    - `mcschemes.tools.parser` now is a package.
        - Sub-package `mcschemes.tools.parser.converters` is added to contains dedicated converters.
- **Typing:** `typing-extensions` was used instead of stdlib `typing` for better backward-compatibility for type annotation.
- **Schema:** Several changes for SHA-1 hexdigest container:
    - The comparison between two `mcsehemes.specials.Sha1Sum` instances now is based on the case-insensitive form of the `hexdigest` attribute of
      both.
    - The exception class `mcschemes.specials.ChecksumMismatch` is now exposed.
- **Parsing:** `mcschemes.tools.parser.parse()` now will check the second argument `scheme` in more robust way.

### Fixed

- **Tooling:** Fixed a mistake when comparing the OS name in the function `mcschemes.tools.rules.isAllow()`.

## [0.2.0] - 2025-12-11

### Added

- **Project metadata:** Added `MANIFEST.in` for setuptools.
- **Schema:** Added an SHA-1 hexdigest container type for `sha1`/`hash` fields (un-)structuring. Its definition can be found at:
  `mcschemes.specials.Sha1Sum`.
- **Tooling:** Added some tool functions to calculate a set of rules (iterable of `mcschemes.clientmanifest.nodes.RuleEntry` instances) means allow or
  disallow some operation, such as append an argument or download a library file.

### Changes

- **Project metadata:** Declared build backend `setuptools` into `pyproject.toml`.
- **Project metadata:** According to [PEP 561](https://peps.python.org/pep-0561), an empty `py.typed` is added into the root directory of package.
- **Project metadata:** Corrected the date format for all tier-2 titles in this version history file.
- **Organizational:** Moved `typings.py` to the root directory of package.

## [0.1.0.post1] - 2025-12-05

### Changes

- **Project metadata:** Added project urls into `pyproject.toml`.
- **Project metadata:** Added disclaimer in `README.md`.

## [0.1.0] - 2025-12-04

### Added

The initial release.