sourceafJson::Json.fan


** (Service) - 
** A one-stop shop for all your JSON mapping needs!
** 
** The methods in this service may be summarised as follows:
** 
** ![JSON Methods]`jsonMethods.png`
** 
@Js
const mixin Json {
    
    ** Creates a new 'Json' instance with the given inspectors.
    static new make(JsonTypeInspectors? inspectors := null, JsonReader? reader := null, JsonWriter? writer := null) {
        JsonImpl(inspectors ?: JsonTypeInspectors(), reader ?: JsonReader(), writer ?: JsonWriter())
    }
    
    ** Returns the underlying 'EntityConverter' instance. 
    abstract EntityConverter converter()

    ** Returns the underlying 'JsonTypeInspectors' instance. 
    abstract JsonTypeInspectors inspectors()

    ** Returns the underlying 'JsonReader' instance. 
    abstract JsonReader jsonReader()

    ** Returns the underlying 'JsonWriter' instance. 
    abstract JsonWriter jsonWriter()

    ** Reads the the given JSON and (optionally) converts it to a Fantom entity instance.
    ** 
    ** The given JSON is read into a 'jsonObj'.
    ** Should 'entityType' not be null then the 'jsonObj' is converted to a Fantom entity.
    abstract Obj? readJson(Str? json, Type? entityType := null)

    ** Translates the given JSON to its Fantom List representation.
    ** 
    ** Convenience for '(Obj?[]?) readJson(json, null)'
    abstract Obj?[]? readJsonAsList(Str? json)
    
    ** Translates the given JSON to its Fantom Map representation.
    ** 
    ** Convenience for '([Str:Obj?]?) readJson(json, null)'
    abstract [Str:Obj?]? readJsonAsMap(Str? json)
    
    ** Converts the given obj to JSON.
    **  
    ** If 'entityType' is not null then the given 'obj' is taken to be an entity and is first 
    ** converted to an 'jsonObj'. The 'jsonObj' is then written out to JSON.
    ** 
    ** 'prettyPrintOptions' may be either a 'PrettyPrintOptions' instance, or just 'true' to enable 
    ** pretty printing with defaults.
    abstract Str writeJson(Obj? obj, Type? entityType := null, Obj? prettyPrintOptions := null)

    ** Converts the given entity instance to its 'jsonObj' representation.
    ** 
    ** If 'entityType' is 'null' it defaults to 'entity.typeof()'.
    abstract Obj? fromEntity(Obj? entity, Type? entityType := null)
    
    ** Converts the given 'jsonObj' to a Fantom entity instance.
    abstract Obj? toEntity(Obj? jsonObj, Type entityType)
}

@Js
internal const class JsonImpl : Json {  
    override const EntityConverter      converter
    override const JsonTypeInspectors   inspectors
    override const JsonReader           jsonReader
    override const JsonWriter           jsonWriter
    
    new make(JsonTypeInspectors inspectors, JsonReader jsonReader, JsonWriter jsonWriter) {
        this.converter  = EntityConverter(inspectors)
        this.inspectors = inspectors
        this.jsonReader = jsonReader
        this.jsonWriter = jsonWriter
    }
    
    override Obj? readJson(Str? json, Type? entityType := null) {
        jsonObj := jsonReader.readJson(json)
        fantObj := entityType == null ? jsonObj : converter.toEntity(jsonObj, entityType)
        return fantObj
    }

    override Obj?[]? readJsonAsList(Str? json) {
        jsonReader.readJsonAsList(json)
    }
    
    override [Str:Obj?]? readJsonAsMap(Str? json) {
        jsonReader.readJsonAsMap(json)
    }
    
    override Str writeJson(Obj? obj, Type? entityType := null, Obj? prettyPrintOptions := null) {
        // I could forego the entity check and first convert ALL objs, 
        // but it's a bucket load faster not to when just serialising out a given jsonObj
        jsonObj := entityType == null ? obj : converter.fromEntity(obj, entityType)
        json    := jsonWriter.writeJson(jsonObj, prettyPrintOptions)
        return json
    }
    
    override Obj? fromEntity(Obj? entity, Type? entityType := null) {
        converter.fromEntity(entity, entityType)
    }
    
    override Obj? toEntity(Obj? jsonObj, Type entityType) {
        converter.toEntity(jsonObj, entityType)
    }
}