Contents

nixzhu/ananda

JSON model decoding based on [yyjson](https://github.com/ibireme/yyjson).

Example

Consider the following JSON:

{
    "profile": {
        "nickname": "NIX",
        "username": "@nixzhu@mastodon.social",
        "avatar": {
            "url": "https://example.com/nixzhu.png",
            "width": 200,
            "height": 200
        }
    },
    "toots": [
        {
            "id": 1,
            "content": "Hello World!",
            "created_at": "2024-10-05T09:41:00.789Z"
        },
        {
            "id": 2,
            "content": "How do you do?",
            "created_at": "2025-04-29T22:23:24.567Z"
        }
    ]
}

We can create models conforming to the AnandaModel protocol as follows:

import Foundation
import Ananda

struct Mastodon: AnandaModel {
    let profile: Profile
    let toots: [Toot]

    init(json: AnandaJSON) {
        profile = .decode(from: json.profile)
        toots = json.toots.array().map { .decode(from: $0) }
    }
}

extension Mastodon {
    struct Profile: AnandaModel {
        let nickname: String
        let username: String
        let avatar: Avatar

        init(json: AnandaJSON) {
            nickname = json.nickname.string()
            username = json.username.string()
            avatar = .decode(from: json.avatar)
        }
    }
}

extension Mastodon.Profile {
    struct Avatar: AnandaModel {
        let url: URL
        let width: Double
        let height: Double

        init(json: AnandaJSON) {
            url = json["url"].url()
            width = json.width.double()
            height = json.height.double()
        }
    }
}

extension Mastodon {
    struct Toot: AnandaModel {
        let id: Int
        let content: String
        let createdAt: Date

        init(json: AnandaJSON) {
            id = json.id.int()
            content = json.content.string()
            createdAt = json.created_at.date()
        }
    }
}

To decode a Mastodon instance from a JSON string:

let mastodon = Mastodon.decode(from: jsonString)

Or, if you already have JSON data:

let mastodon = Mastodon.decode(from: jsonData)

To decode a specific JSON branch, for example profile.avatar, specify its path:

let avatar = Mastodon.Profile.Avatar.decode(from: jsonData, path: ["profile", "avatar"])

To decode an array (e.g., toots):

let toots = [Mastodon.Toot].decode(from: jsonData, path: ["toots"])

Or decode only the first toot:

let toot = Mastodon.Toot.decode(from: jsonData, path: ["toots", 0])

Installation

Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler.

Once you have a Swift package set up, add Ananda as a dependency in your Package.swift file:

// In Package.swift

dependencies: [
    .package(url: "https://github.com/nixzhu/Ananda.git", from: "1.2.0"),
]

Typically, you will want to depend on the Ananda product:

// In a target's dependencies

.product(name: "Ananda", package: "Ananda")

Swift Macro

With AnandaMacros, you can use macros to eliminate the need for initialization methods, as shown below:

import Foundation
import Ananda
import AnandaMacros

@AnandaInit
struct Mastodon: AnandaModel {
    let profile: Profile
    let toots: [Toot]
}

extension Mastodon {
    @AnandaInit
    struct Profile: AnandaModel {
        let nickname: String
        let username: String
        let avatar: Avatar
    }
}

extension Mastodon.Profile {
    @AnandaInit
    struct Avatar: AnandaModel {
        let url: URL
        let width: Double
        let height: Double
    }
}

extension Mastodon {
    @AnandaInit
    struct Toot: AnandaModel {
        let id: Int
        let content: String
        @AnandaKey("created_at")
        let createdAt: Date
    }
}

Simple and clean, right?

Benchmark

See AnandaBenchmark.

Tool

You can use Ducky Model Editor to generate AnandaModel from JSON, saving you time.

[Ducky Model Editor]

[Ducky Model Editor]

Package Metadata

Repository: nixzhu/ananda

Default branch: main

README: README.md