Freddy - A reusable framework for parsing JSON in Swift.

Related tags

JSON Freddy
Overview

Why Freddy?

Parsing JSON elegantly and safely can be hard, but Freddy is here to help. Freddy is a reusable framework for parsing JSON in Swift. It has three principal benefits.

First, Freddy provides a type safe solution to parsing JSON in Swift. This means that the compiler helps you work with sending and receiving JSON in a way that helps to prevent runtime crashes.

Second, Freddy provides an idiomatic solution to JSON parsing that takes advantage of Swift's generics, enumerations, and functional features. This is all provided without the pain of having to memorize our documentation to understand our magical custom operators. Freddy does not have any of those. If you feel comfortable writing Swift (using extensions, protocols, initializers, etc.), then you will not only understand how Freddy is organized, but you will also feel comfortable using Freddy.

Third, Freddy provides great error information for mistakes that commonly occur while parsing JSON. If you subscript the JSON object with a key that is not present, you get an informative error. If your desired index is out of bounds, you get an informative error. If you try to convert a JSON value to the wrong type, you get a good error here too.

So, Freddy vs. JSON, who wins? We think it is Freddy.

Usage

This section describes Freddy's basic usage. You can find more examples on parsing data, dealing with errors, serializing JSON instances into NSData, and more in the Wiki. You can read the documentation to see the full API.

Deserialization: Parsing Raw Data

Basic Usage

Consider some example JSON data:

{
    "success": true,
    "people": [
        {
            "name": "Matt Mathias",
            "age": 32,
            "spouse": true
        },
        {
            "name": "Sergeant Pepper",
            "age": 25,
            "spouse": false
        }
    ],
    "jobs": [
        "teacher",
        "judge"
    ],
    "states": {
        "Georgia": [
            30301,
            30302,
            30303
        ],
        "Wisconsin": [
            53000,
            53001
        ]
    }
}

Here is a quick example on how to parse this data using Freddy:

let data = getSomeData()
do {
    let json = try JSON(data: data)
    let success = try json.getBool(at: "success")
    // do something with `success`
} catch {
    // do something with the error
}

After we load in the data, we create an instance of JSON, the workhorse of this framework. This allows us to access the values from the JSON data. We try because the data may be malformed and the parsing could generate an error. Next, we access the "success" key by calling the getBool(at:) method on JSON. We try here as well because accessing the json for the key "success" could fail - e.g., if we had passed an unknown key. This method takes two parameters, both of which are used to define a path into the JSON instance to find a Boolean value of interest. If a Bool is found at the path described by "success", then getBool(at:) returns a Bool. If the path does not lead to a Bool, then an appropriate error is thrown.

Use Paths to Access Nested Data with Subscripting

With Freddy, it is possible to use a path to access elements deeper in the json structure. For example:

let data = getSomeData()
do {
    let json = try JSON(data: data)
    let georgiaZipCodes = try json.getArray(at: "states","Georgia")
    let firstPersonName = try json.getString(at: "people",0,"name")
} catch {
    // do something with the error
}

In the code json.getArray(at: "states","Georgia"), the keys "states" and "Georgia" describe a path to the Georgia zip codes within json. Freddy's parlance calls this process "subscripting" the JSON. What is typed between the parentheses of, for example, getArray(at:) is a comma-separated list of keys and indices that describe the path to a value of interest.

There can be any number of subscripts, and each subscript can be either a String indicating a named element in the JSON, or an Int that represents an element in an array. If there is something invalid in the path such as an index that doesn't exist in the JSON, an error will be thrown.

More on Subscripting

JSONDecodable: Deserializing Models Directly

Now, let's look an example that parses the data into a model class:

let data = getSomeData()
do {
    let json = try JSON(data: data)
    let people = try json.getArray(at: "people").map(Person.init)
    // do something with `people`
} catch {
    // do something with the error
}

Here, we are instead loading the values from the key "people" as an array using the method getArray(at:). This method works a lot like the getBool(at:) method you saw above. It uses the path provided to the method to find an array. If the path is good, the method will return an Array of JSON. If the path is bad, then an appropriate error is thrown.

We can then call map on that JSON array. Since the Person type conforms to JSONDecodable, we can pass in the Person type's initializer. This call applies an initializer that takes an instance of JSON to each element in the array, producing an array of Person instances.

Here is what JSONDecodable looks like:

public protocol JSONDecodable {
    init(json: JSON) throws
}

It is fairly simple protocol. All it requires is that conforming types implement an initializer that takes an instance of JSON as its sole parameter.

To tie it all together, here is what the Person type looks like:

public struct Person {
    public let name: String
    public let age: Int
    public let spouse: Bool
}

extension Person: JSONDecodable {
    public init(json value: JSON) throws {
        name = try value.getString(at: "name")
        age = try value.getInt(at: "age")
        spouse = try value.getBool(at: "spouse")
    }
}

Person just has a few properties. It conforms to JSONDecodable via an extension. In the extension, we implement a throwsing initializer that takes an instance of JSON as its sole parameter. In the implementation, we try three functions: 1) getString(at:), 2) getInt(at:), and 3) getBool(at:). Each of these works as you have seen before. The methods take in a path, which is used to find a value of a specific type within the JSON instance passed to the initializer. Since these paths could be bad, or the requested type may not match what is actually inside of the JSON, these methods may potentially throw an error.

Serialization

Freddy's serialization support centers around the JSON.serialize() method.

Basic Usage

The JSON enumeration supports conversion to Data directly:

let someJSON: JSON = 
do {
    let data: Data = try someJSON.serialize()
} catch {
    // Handle error
}

JSONEncodable: Serializing Other Objects

Most of your objects aren't Freddy.JSON objects, though. You can serialize them to Data by first converting them to a Freddy.JSON via JSONEncodable.toJSON(), the sole method of the JSONEncodable protocol, and then use serialize() to convert the Freddy.JSON to Data:

let myObject: JSONEncodable = 

// Object -> JSON -> Data:
let objectAsJSON: JSON = myObject.toJSON()
let data: Data = try objectAsJSON.serialize()

// More concisely:
let dataOneLiner = try object.toJSON().serialize()

Freddy provides definitions for common Swift datatypes already. To make your own datatypes serializable, conform them to JSONEncodable and implement that protocol's toJSON() method:

extension Person: JSONEncodable {
    public func toJSON() -> JSON {
        return .dictionary([
            "name": .string(name),
            "age": .int(age),
            "spouse": .bool(spouse)])
    }
}

Getting Started

Freddy requires iOS 8.0, Mac OS X 10.9, watchOS 2.0, or tvOS 9.0. Linux is not yet supported.

You have a few different options to install Freddy.

Carthage

Add us to your Cartfile:

3.0 ">
github "bignerdranch/Freddy" ~> 3.0

After running carthage bootstrap, add Freddy.framework to the "Linked Frameworks and Libraries" panel of your application target. Read more.

CocoaPods

Add us to your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'Freddy'

Then run pod install.

Submodules

  1. git submodule add https://github.com/bignerdranch/Freddy.git Vendor/Freddy
  2. Drag Freddy.xcodeproj into your Xcode project.
  3. Add Freddy.framework to the "Linked Frameworks and Libraries" panel of your application target.

Carthage can be used to check out dependencies and maintain Git submodule state as well.

Swift Package Manager

Add us to your Package.swift:

import PackageDescription

let package = Package(
    name: "My Nerdy App",
    dependencies: [
        .Package(url: "https://github.com/bignerdranch/Freddy.git", majorVersion: 3),
    ]
)

iOS 7

If you would like to use Freddy with iOS 7, then you will need to use a previous release of Freddy.

Setting Breakpoint Errors

It can be helpful to set breakpoints for errors when you start working with a new set of JSON. This allows you to explore the structure of the JSON when you break. In particular, you will likely want to set a breakpoint for Freddy's JSON.Error so that you can inspect what went wrong.

Here is how you can set this sort of breakpoint:

  1. Go to the Breakpoint navigator

  2. Click the "+" button in the bottom left corner

Breakpoint navigator

  1. Select "Add Swift Error Breakpoint"

Add Error Breakpoint

Now you have a breakpoint that will only trigger when a Swift error is generated. But your program will break whenever any Swift error is thrown. What if you only want to break for Freddy's JSON.Error error?

You can edit the breakpoint to add a filter:

  1. Right-click your new error breakpoint

  2. Select Edit Breakpoint...

Edit Breakpoint

  1. A window will appear with a text box for "Type"

  2. Enter JSON.Error

Error Type

And that is pretty much it! You now have an error breakpoint that will only trigger when errors of type JSON.Error are thrown. Take a look at the framework's tests for further examples of usage. The Wiki also have a lot of very useful information.

Comments
  • Large integers cause overflows, especially on 32-bit hosts

    Large integers cause overflows, especially on 32-bit hosts

    Follow-on from #57. Consensus was that JSON.Int should continue to use Swift.Int as its associated type, but that still leaves the issue of overflows at hand. The stdlib asserts inside the parser, generally bringing down the whole app as a result.

    Options include:

    • Switch the parser to the &+ et. al. operators and explicitly allow overflows.
    • Switch the parser to the addWithOverflow(_:_:) et. al. functions, catch overflows, and throw errors, thus cancelling the parse.
    • Switch the parser to the addWithOverflow(_:_:) et. al. functions, catch overflows, and return Strings for big integers. This might require some workflow changes in the parser.
    • Switch the parser to the addWithOverflow(_:_:) et. al. functions, catch overflows, and return Doubles for big integers. (undesirable, but I mention it)

    Workaround

    If this is a problem for you today, you can work around it by using Apple's parser rather than Freddy's via JSON(data:usingParser:) and specifying the parser as NSJSONSerialization.self.

    // assuming you have data: NSData
    let json = try JSON(data: data, usingParser: NSJSONSerialization.self)
    

    For a bit more discussion around this, see this comment below.

    Discussion Summary

    Eventual decision was to avoid overflow by treating numbers that would overflow as strings. The value can be pulled out and then parsed from the string if needed by the user of the API. Implemented in #152 and expected to land in Freddy v2.1.

    bug api change 
    opened by zwaldowski 52
  • Subscript improvements

    Subscript improvements

    Organizes subscripts implementations for clarity.

    • Rewrites the subscripts so that they all use the no-arguments versions directly
    • Makes the solution for #75 easier to read (ping @natechan).
    • Elides some generics that were preventing compiler optimization, yielding a minor performance improvement on deserialization (JSON -> MyType)
    • Addresses #85.

    API removals are made; however, there are no changes to existing user code because the varargs subscripts can handle 0 arguments again. This and the changes to the organization of the JSONSubscripting file may affect documentation generation.

    opened by zwaldowski 10
  • Use antitypical result

    Use antitypical result

    This PR is overly large because of the swap out in Carthage. It doesn't make sense to split it up, though, since the library needs to be updated for the new Result.

    Closes #19.

    @zwaldowski / @mdmathias r?

    opened by jgallagher 10
  • JSONEncodable Performance Issue

    JSONEncodable Performance Issue

    On my current project, we are trying to use Freddy to encode our structs to send in POSTs. Unfortunately, our model has a lot of optionals and it seems the way we're trying to implement toJSON is causing issues with the Swift compiler. I'm currently getting: "Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions". Here is what we're trying to encode:

    extension PerformedSurvey : JSONEncodable {
    
        func toJSON() -> JSON {
            let dictionary: [String : JSON] = [
                "id" : self.id?.toJSON() ?? JSON.Null,
                "name" : self.name?.toJSON() ?? JSON.Null,
                "created" : self.created?.description.toJSON() ?? JSON.Null,
                "completed" : self.completed?.description.toJSON() ?? JSON.Null,
                "dateSurveyed" : self.dateSurveyed?.description.toJSON() ?? JSON.Null,
                "savedDate" : self.savedDate?.description.toJSON() ?? JSON.Null,
                "status" : self.status?.toJSON() ?? JSON.Null,
                "customField1" : self.customField1?.toJSON() ?? JSON.Null,
                "customField2" : self.customField2?.toJSON() ?? JSON.Null,
                "customField3" : self.customField3?.toJSON() ?? JSON.Null,
                "customField4" : self.customField4?.toJSON() ?? JSON.Null,
                "textRoom" : self.textRoom?.toJSON() ?? JSON.Null,
                "surveyorFirstName" : self.surveyorFirstName?.toJSON() ?? JSON.Null,
                "surveyorLastName" : self.surveyorLastName?.toJSON() ?? JSON.Null,
                "survey" : self.survey.id.toJSON(),
                "department" : self.department.id.toJSON(),
                "location" : self.location.id
            ]
            return JSON.Dictionary(dictionary)
        }
    
    }
    

    I'm trying to be as explicit as I can think to be in the types. If I change all the optionals to be force unwrapping then it compiles without any issues. Is there a way I can structure this better or am I going to have to split out the construction of this dictionary into a ton of different statements? Thanks!

    opened by justbaum30 9
  • Converting a Dictionary to JSON

    Converting a Dictionary to JSON

    Is there a easy way to convert a dictionary (i.e of type [String : String], [String, AnyObject],...) without writing all this boiler plate?

    var tempDict: [String : JSON]
    dict.forEach { k, v in
        if let v = v as? String {
            tempDict[k] = JSON.String(v)
        } else if let v = v as? Int {
            tempDict[k] = JSON.Int(v)
        } else if let v = v as? Bool {
            tempDict[k] = JSON.Bool(v)
        }
    }
    let json = JSON.Dictionary(tempDict)
    

    If not should we add a new initializer?

    question 
    opened by lucatorella 9
  • Freddy's current behavior when a JSON dictionary has multiple keys of same name is not consistent with other systems.

    Freddy's current behavior when a JSON dictionary has multiple keys of same name is not consistent with other systems.

    Currently if a JSON source, which contains a dictionary and that dictionary contains multiple uses of the same key, is parsed with Freddy the second value is returned. In comparison in my testing SwiftyJSON and Argo (which are both based on NSJSONSerialization) the first value is returned.

    I would like to work with @jeremy-w on a PR to address this (during our testing pair time) but first I need some guidance on what should the behavior be. In my gut I think we should reject the JSON as invalid, and should they want to accept the faulty JSON tell them to pass in NSJSONSerialization as the parser.

    opened by zorn 9
  • Use `IntMax` to represent integers

    Use `IntMax` to represent integers

    This PR is to the swift-2_0 branch, and is dependent on #50. The relevant commit, at time of writing, is 2e81fb6. This PR supersedes #54 and all previous renditions of this concept.

    As in the ECMAScript standard, JavaScript and JSON represent all numbers as double.

    This causes two problems:

    • Downcasting from an NSNumber containing a int64_t to Swift.Int on a 32-bit platform using the NSJSONSerialization parser will unexpectedly overflow, but not trap.
    • Parsing a 64-bit integer on a 32-bit platform using our custom parser will overflow and trap.

    This PR switches the represented case of JSON.Int to Swift.IntMax to mitigate these problems. It doesn't mitigate them completely; if a JSON-compliant-but-not-JavaScript backend sends us a malformed integer that is greater than 64-bits, we'll still overflow, but that's exceedingly unlikely since the databases and server-side languages are generally subject to the same integer limits we are. (If someone's storing user IDs as a BigDecimal… god help them.)

    Pros:

    • Less crashy-crashy.
    • Literals still work.
    • With a creative initializer generic over IntegerType, JSON(myInt) for myInt: Int still works.

    Cons:

    • Breaking change for those who have used Int in their parsed types.
    • JSON.Int(myInt) stops working for myInt: Int.
    invalid 
    opened by zwaldowski 9
  • Concrete error types, Result unification

    Concrete error types, Result unification

    This is against the swift-2_0 branch.

    • Gives JSONParser an Error type encompassing all individual parser errors.
    • Gives JSON.Error (née JSON.ErrorCode) associated data, rather than the problem/reason strings.
    • Moves everything onto Result<T, Error>. Most uses use JSON.Error as the error, and upcasting to NSError is generally avoided.
    • Replaces JSONResult with protocol extensions and a type alias.

    This PR supersedes #44 and #49 because I broke Git.

    opened by zwaldowski 9
  • How to decode json with a root array?

    How to decode json with a root array?

    I'm trying to decode json with a root array containing dictionaries like this:

    [
      {
        "identifier": "id 1",
        "name": "name 1",
        "image": "path 1"
      },
      {
        "identifier": "id 2",
        "name": "name 2",
        "image": "path 2"
      }
    ]
    

    How would I map those dictionaries into model structs?

    struct Model {
        let identifier: String
        let name: String
        let image: String
    }
    extension Model: JSONDecodable {
        init(json value: JSON) throws {
            identifier = try value.string("identifier")
            name = try value.string("name")
            image = try value.string("image")
        }
    }
    
    opened by trispo 8
  • Dictionary: JSONEncodable

    Dictionary: JSONEncodable

    I have an extension to Dictionary that serves my purposes pretty well of making the Dictionary type conform to JSONEncodable. However, I can't quite figure out how to make it accept dictionaries with Optional value types.

    extension Dictionary: JSONEncodable {
        public func toJSON() -> JSON {
            var convertibleDictionary: [String: JSON] = [:]
            for (key, value) in self {
                // TODO: Figure out a way to support Optionals
                if let value = value as? JSONEncodable {
                    convertibleDictionary[String(key)] = value.toJSON()
                } else {
                    return JSON.Null
                }
            }
    
            return JSON.Dictionary(convertibleDictionary)
        }
    }
    

    Any ideas? Or am I going about this entirely the wrong way?

    Thanks.

    opened by snown 8
  • From JSON to NSData?

    From JSON to NSData?

    Let's suppose I've a class MyStruct that conforms to the JSONEncodable and protocol. With the toJSON() method I can create a Freddy.JSON value, but how can I get a NSData/String from that? I'd like a NSData/String that can be used by the JSONDecodable protocol.

    I tried myStruct.toJSON.description but the string returned contain escape chars and can't be converted back.

    opened by lucatorella 8
  • Fixing missingKeyBecomesNil being recursive

    Fixing missingKeyBecomesNil being recursive

    Here's a fix for the .missingKeyBecomesNil Option catching errors from decode calls below.

    For instance:

         struct A: JSONDecodable {
                let b: B?
                
                init(json: JSON) throws {
                    b = try json.decode(at: "b", alongPath: [.missingKeyBecomesNil])
                }
                
            }
            
            struct B: JSONDecodable {
                let string: String
                
                init(json: JSON) throws {
                    string = try json.decode(at: "string")
                }
            }
    

    If I were to call decode with the following invalid json:

    {
        "b": {
        
        }
    }
    

    The previous implementation wouldn't throw an error but return A(b: nil)

    If you still want the previous functionality you can use the parameter applyRecursively.

     b = try json.decode(at: "b", alongPath: [.missingKeyBecomesNil]) // Will throw an error
     b = try json.decode(at: "b", alongPath: [.missingKeyBecomesNil], applyRecursively: true) // nil
    
    opened by nerdsupremacist 2
  • Linux support

    Linux support

    I've made some changes, so that Freddy will build and work on Linux. Tests can be run with swift test in the Freddy directory.

    What appears to work: Freddy (disclaimer: for my use) and 107 of its tests.

    What doesn't work: Tests that rely on Bundle(for:), because it has yet to be implemented in Foundation. These include some of the JSONSubscriptingTests, JSONTests and all of the JSONSerializingTests. Note that the tests are (not necessarily) failing. They just won't run at the moment. For now, I've commented out these tests and added some conditional compilation statements to prevent the code from being reached on Linux. Of the 107 tests that run properly, only a single fails:

    /project/Tests/FreddyTests/JSONParserTests.swift:333: error: JSONParserTests.testOverflowingIntResultsInStringWithNSJSONSerializationParser : XCTAssertEqual failed: ("nil") is not equal to ("Optional(1.8446744073709552e+19)") - as double

    I have yet to look for the culprit.

    opened by agisboye 0
Releases(3.0.3)
Owner
Big Nerd Ranch
Big Nerd Ranch
Swift-json - High-performance json parsing in swift

json 0.1.4 swift-json is a pure-Swift JSON parsing library designed for high-per

kelvin 43 Sep 26, 2022
Ss-json - High-performance json parsing in swift

json 0.1.1 swift-json is a pure-Swift JSON parsing library designed for high-per

kelvin 43 Sep 26, 2022
[Deprecated] A shiny JSON parsing library in Swift :sparkles: Loved by many from 2015-2021

?? Deprecation Notice ?? Gloss has been deprecated in favor of Swift's Codable framework. The existing Gloss source is not going away, however updates

Harlan Kellaway 1.6k Oct 18, 2022
Precise JSON decimal parsing for Swift 🧮

PreciseDecimal Introduction Swift has long suffered a problem with its Decimal type: unapparent loss of precision. This happens with all common ways o

David Roman 21 Oct 21, 2022
Developed with use Swift language. As a third party library used SDWebImage. JSON parsing using URLSession with TMDB API. This app provide by the Core Data structure.

Capstone Project ?? About Developed with use Swift language. As a third party library used SDWebImage. JSON parsing using URLSession with TMDB API. Ad

Ensar Batuhan Unverdi 9 Aug 22, 2022
Networking, JSON Parsing, APIs and Core Location

Clima Our Goal It’s time to take our app development skills to the next level. We’re going to introduce you to the wonderful world of Application Prog

Dessana Caldeira M. Santos 1 Nov 25, 2021
📱 A comprehensive test task for creating an autolayout interface, requesting an API and JSON parsing from Effective Mobile.

ECOMMERCE A comprehensive test task for creating an autolayout interface, requesting an API and JSON parsing from Effective Mobile. ??‍?? Design ✨ Fea

Daniel Tvorun 4 Nov 21, 2022
JSEN (JSON Swift Enum Notation) is a lightweight enum representation of a JSON, written in Swift.

JSEN /ˈdʒeɪsən/ JAY-sən JSEN (JSON Swift Enum Notation) is a lightweight enum representation of a JSON, written in Swift. A JSON, as defined in the EC

Roger Oba 8 Nov 22, 2022
JSON-Practice - JSON Practice With Swift

JSON Practice Vista creada con: Programmatic + AutoLayout Breve explicación de l

Vanesa Giselle Korbenfeld 0 Oct 29, 2021
Swift parser for JSON Feed — a new format similar to RSS and Atom but in JSON.

JSONFeed Swift parser for JSON Feed — a new format similar to RSS and Atom but in JSON. For more information about this new feed format visit: https:/

Toto Tvalavadze 31 Nov 22, 2021
JSONNeverDie - Auto reflection tool from JSON to Model, user friendly JSON encoder / decoder, aims to never die

JSONNeverDie is an auto reflection tool from JSON to Model, a user friendly JSON encoder / decoder, aims to never die. Also JSONNeverDie is a very important part of Pitaya.

John Lui 454 Oct 30, 2022
HandyJSON is a framework written in Swift which to make converting model objects to and from JSON easy on iOS.

HandyJSON To deal with crash on iOS 14 beta4 please try version 5.0.3-beta HandyJSON is a framework written in Swift which to make converting model ob

Alibaba 4k Nov 14, 2022
ObjectMapper is a framework written in Swift that makes it easy for you to convert your model objects to and from JSON.

ObjectMapper is a framework written in Swift that makes it easy for you to convert your model objects (classes and structs) to and from J

Tristan Himmelman 9k Nov 20, 2022
Swift/Obj-C HTTP framework with a focus on REST and JSON

Now Archived and Forked PMHTTP will not be maintained in this repository going forward. Please use, create issues on, and make PRs to the fork of PHMT

Postmates Inc. 509 Sep 4, 2022
An iOS framework for creating JSON-based models. Written in Swift.

An iOS framework for creating JSON-based models. Written in Swift (because it totally rules!) Requirements iOS 8.0+ Xcode 7.3 Swift 2.2 Installation E

Oven Bits 448 Nov 8, 2022
A lightweight CSS parser for parsing and creating CSS stylesheets

SwiftCSSParser A lightweight CSS parser for Swift that uses cssparser (cpp) under the hood. Basic usage Here's a simple code snippet to get you starte

null 9 Jul 20, 2022
This framework implements a strict JSON parser and generator in Objective-C.

SBJson 5 Chunk-based JSON parsing and generation in Objective-C. Overview SBJson's number one feature is stream/chunk-based operation. Feed the parser

null 3.8k Nov 25, 2022
Magical Data Modeling Framework for JSON - allows rapid creation of smart data models. You can use it in your iOS, macOS, watchOS and tvOS apps.

JSONModel - Magical Data Modeling Framework for JSON JSONModel allows rapid creation of smart data models. You can use it in your iOS, macOS, watchOS

JSONModel 6.9k Nov 17, 2022
A fast, convenient and nonintrusive conversion framework between JSON and model. Your model class doesn't need to extend any base class. You don't need to modify any model file.

MJExtension A fast, convenient and nonintrusive conversion framework between JSON and model. 转换速度快、使用简单方便的字典转模型框架 ?? ✍??Release Notes: more details Co

M了个J 8.5k Nov 15, 2022