Skip to content

Commit

Permalink
std/encoding, std/encoding/json: improve custom encoding and decoding…
Browse files Browse the repository at this point in the history
… method support
  • Loading branch information
mertcandav committed Nov 28, 2024
1 parent 5f8f311 commit cb2eae9
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 27 deletions.
51 changes: 51 additions & 0 deletions std/encoding/encoding.jule
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2024 The Jule Programming Language.
// Use of this source code is governed by a BSD 3-Clause
// license that can be found in the LICENSE file.

// Implements custom encoder method for JSON encoding.
// For types with this method, this method is called instead of the default
// encoding strategy and custom encoding is performed. The returned bytes must
// be a valid JSON value. Otherwise, EncodeError.EncodeJSON exception is thrown.
// Throwing any exception is considered valid. The thrown exception will be
// forwarded by the Encode. Successful encoding should not throw any exceptions.
trait JSONEncoder {
fn EncodeJSON(self)!: []byte
}

// Implements custom decoder method for JSON decoding.
// For types with this method, this method is called instead of the default
// decoding strategy and custom decoding is performed. The data parameter is the
// corresponding data equivalent and is always a validated, error-free JSON data.
// It is a mutable copy taken from the data used for decoding, so any change may
// cause mutation in the main data. According to the defined behavior,
// decoder methods should not mutate the content of the data.
// Throwing any exception is considered valid. The thrown exception will be
// forwarded by the Decode. Successful decoding should not throw any exceptions
// and self should be changed as required.
trait JSONDecoder {
fn DecodeJSON(mut self, data: []byte)!
}

// Implements custom encoder method for text encoding.
// For types with this method, this method is called instead of the default
// encoding strategy and custom encoding is performed. Throwing any exception
// is considered valid. The thrown exception will be forwarded by the encoder.
// Successful encoding should not throw any exceptions.
// It should return UTF-8 encoded text in bytes.
trait TextEncoder {
fn EncodeText(self)!: []byte
}

// Implements custom decoder method for text decoding.
// For types with this method, this method is called instead of the default
// decoding strategy and custom decoding is performed. The data parameter is the
// corresponding data equivalent and is always UTF-8 encoded text in bytes.
// It may be a mutable copy taken from the data used for decoding, so any change may
// cause mutation in the main data. According to the defined behavior,
// decoder methods should not mutate the content of the data.
// Throwing any exception is considered valid. The thrown exception will be
// forwarded by the decoder. Successful decoding should not throw any exceptions
// and self should be changed as required.
trait TextDecoder {
fn DecodeText(mut self, data: []byte)!
}
50 changes: 34 additions & 16 deletions std/encoding/json/decode.jule
Original file line number Diff line number Diff line change
Expand Up @@ -466,13 +466,42 @@ impl jsonDecoder {
}
}
}
b := self.data[self.i]
match b {
| '"': // String literal
const for _, method in tt.Decl().Methods() {
const match {
| method.Name() == "DecodeText":
const params = method.Params()
const match {
| len(params) == 2 && params[0].Mutable():
// Checking params[0] above is safe, because methods
// always same the receiver parameter. However,
// check params[1] here to avoid index-overflow error.
const match {
| !params[1].Mutable():
const m = comptime::ValueOf(t).Method(method.Name())
const match type m.Type().Params()[1].Type() {
| []byte:
const match m.Type().Result().Kind() {
| comptime::Kind.Void:
lit := self.scanValidLit() else { error(error) }
m.Unwrap()(lit[1:len(lit)-1]) else { error(error) }
ret true
}
}
}
}
}
}
}
}
ret false
}

fn value1[T](self, mut &t: T)! {
// Before using the default decoding strategy, look for the custom decoder
// method and use it, if any. If not exist any custom decoder method,
// methods and use it, if any. If not exist any custom decoder method,
// fallback to default decoding.
ok := self.tryCustomDecode(t) else { error(error) }
if ok {
Expand Down Expand Up @@ -564,7 +593,7 @@ impl jsonDecoder {

fn value[T](self, mut &t: T)! {
// Before using the default decoding strategy, look for the custom decoder
// method and use it, if any. If not exist any custom decoder method,
// methods and use it, if any. If not exist any custom decoder method,
// fallback to default decoding.
ok := self.tryCustomDecode(t) else { error(error) }
if ok {
Expand Down Expand Up @@ -653,20 +682,9 @@ impl jsonDecoder {
// it is recommended that a data type can carry a maximum of 10000 nested data.
// However, tousands of nested-data is always risky even below 10000.
//
// Custom decoder methods are supported. Any type that supports it must define
// an appropriate decoder method:
//
// fn DecodeJSON(self, data: []byte)!
//
// For types with this method, this method is called instead of the default
// decoding strategy and custom decoding is performed. The data parameter is the
// corresponding data equivalent and is always a validated, error-free JSON data.
// It is a mutable copy taken from the data used for decoding, so any change may
// cause mutation in the main data. According to the defined behavior,
// decoder methods should not mutate the content of the data.
// Throwing any exception is considered valid. The thrown exception will be
// forwarded by the Decode. Successful decoding should not throw any exceptions
// and self should be changed as required.
// Supported trait implementations by higher-to-lower precedence
// (having methods without implementing the trait is valid):
// JSONDecoder, TextDecoder
fn Decode[T](data: []byte, mut &t: T)! {
decoder := jsonDecoder{
data: data,
Expand Down
31 changes: 20 additions & 11 deletions std/encoding/json/encode.jule
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ impl jsonEncoder {
error(EncodeError.UnsupportedType)
| tt.Strict() || tt.Kind() == comptime::Kind.Struct:
// Before using the default encoding strategy, look for the custom encoder
// method and use it, if any. If not exist any custom encoder method,
// methods and use it, if any. If not exist any custom encoder method,
// fallback to default encoding.
const for _, method in tt.Decl().Methods() {
const match {
Expand All @@ -374,6 +374,22 @@ impl jsonEncoder {
}
}
}
const for _, method in tt.Decl().Methods() {
const match {
| method.Name() == "EncodeText":
const params = method.Params()
const match {
| len(params) == 1 && !params[0].Mutable():
const m = comptime::ValueOf(t).Method(method.Name())
const match type m.Type().Result() {
| []byte:
bytes := m.Unwrap()() else { error(error) }
self.encodeStr(unsafe::BytesStr(bytes))
ret
}
}
}
}
// Fallback to the defult strategy.
}
const match tt.Kind() {
Expand Down Expand Up @@ -484,16 +500,9 @@ fn encoder(): jsonEncoder {
// it is recommended that a data type can carry a maximum of 10000 nested data.
// However, tousands of nested-data is always risky even below 10000.
//
// Custom encoder methods are supported. Any type that supports it must define
// an appropriate encoder method:
//
// fn EncodeJSON(self)!: []byte
//
// For types with this method, this method is called instead of the default
// encoding strategy and custom encoding is performed. The returned bytes must
// be a valid JSON value. Otherwise, EncodeError.EncodeJSON exception is thrown.
// Throwing any exception is considered valid. The thrown exception will be
// forwarded by the Encode. Successful encoding should not throw any exceptions.
// Supported trait implementations by higher-to-lower precedence
// (having methods without implementing the trait is valid):
// JSONEncoder, TextEncoder
fn Encode[T](t: T)!: []byte {
mut encoder := encoder()
encoder.encode[T, encodeFlagType.Plain](t) else { error(error) }
Expand Down

0 comments on commit cb2eae9

Please sign in to comment.