Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions gateway/router/marshal/json/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package json

import (
"bytes"
"reflect"
"unsafe"

"github.com/francoispqt/gojay"
"github.com/viant/datly/gateway/router/marshal/config"
"github.com/viant/tagly/format/text"
"github.com/viant/xunsafe"
"reflect"
"unsafe"
)

const null = `null`
Expand Down
7 changes: 5 additions & 2 deletions gateway/router/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ func (r *Route) UnmarshalFunc(request *http.Request) shared.Unmarshal {
}

return func(bytes []byte, i interface{}) error {
return r.Marshaller.JSON.JsonMarshaller.Unmarshal(bytes, i, jsonPathInterceptor, request)
if r.Marshaller.JSON.CanUnmarshal() {
return r.Marshaller.JSON.Unmarshal(bytes, i)
}
return r.Marshaller.JSON.RuntimeUnmarshallerEngine().Unmarshal(bytes, i, jsonPathInterceptor, request)
}
}

Expand All @@ -113,7 +116,7 @@ func (r *Route) Init(ctx context.Context, resource *Resource) error {
return nil
}
r._unmarshallerInterceptors = r.Transforms.FilterByKind(marshal.TransformKindUnmarshal)
if err := r.Component.Content.InitMarshaller(r.Component.IOConfig(), r.Output.Exclude, r.BodyType(), r.OutputType()); err != nil {
if err := r.Component.Content.InitMarshaller(r.Component.IOConfig(), r.Output.Exclude, r.BodyType(), r.OutputType(), resource.Resource.LookupType()); err != nil {
return err
}
if r.APIKey != nil {
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ require (
github.com/viant/structql v0.5.4
github.com/viant/toolbox v0.37.0
github.com/viant/velty v0.4.0
github.com/viant/xunsafe v0.10.4-0.20260223225257-275a15956559
github.com/viant/xreflect v0.7.5-0.20260314170600-13f09f37d46e
github.com/viant/xunsafe v0.10.3

golang.org/x/mod v0.28.0
golang.org/x/oauth2 v0.32.0
google.golang.org/api v0.201.0
Expand All @@ -51,7 +52,7 @@ require (
github.com/viant/jsonrpc v0.17.0
github.com/viant/mcp v0.11.0
github.com/viant/mcp-protocol v0.11.0
github.com/viant/structology v0.8.0
github.com/viant/structology v0.8.1-0.20260318224343-dcb808a9bd76
github.com/viant/tagly v0.3.0
github.com/viant/x v0.4.1-0.20260306005005-975ded1e1bef
github.com/viant/xdatly v0.5.4-0.20260306062123-17850ac34977
Expand Down
11 changes: 7 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1198,8 +1198,8 @@ github.com/viant/sqlparser v0.11.1-0.20260224194657-0470849e3588 h1:bnVgWzZzuz2p
github.com/viant/sqlparser v0.11.1-0.20260224194657-0470849e3588/go.mod h1:2QRGiGZYk2/pjhORGG1zLVQ9JO+bXFhqIVi31mkCRPg=
github.com/viant/sqlx v0.21.0 h1:Lx5KXmzfSjSvZZX5P0Ua9kFGvAmCxAjLOPe9pQA7VmY=
github.com/viant/sqlx v0.21.0/go.mod h1:woTOwNiqvt6SqkI+5nyzlixcRTTV0IvLZUTberqb8mo=
github.com/viant/structology v0.8.0 h1:WKdK67l+O1eqsubn8PWMhWcgspUGJ22SgJxUMfiRgqE=
github.com/viant/structology v0.8.0/go.mod h1:Fnm1DyR4gfyPbnhBMkQB5lR6/isYDnncBFO1nCxxmqs=
github.com/viant/structology v0.8.1-0.20260318224343-dcb808a9bd76 h1:LUhy4A9ps2aFP3cBCTw4sMG1QmKWMNG1//vId03utEc=
github.com/viant/structology v0.8.1-0.20260318224343-dcb808a9bd76/go.mod h1:AAFeViwniqua61sTKdOz/zlbLpN5vE4OVhDoiZJaMgA=
github.com/viant/structql v0.5.4 h1:bMdcOpzU8UMoe5OBcyJVRxLAndvU1oj3ysvPUgBckCI=
github.com/viant/structql v0.5.4/go.mod h1:nm9AYnAuSKH7b7pG+dKVxbQrr1Mgp1CQEMvUwwkE+I8=
github.com/viant/tagly v0.3.0 h1:Y8IckveeSrroR8yisq4MBdxhcNqf4v8II01uCpamh4E=
Expand All @@ -1226,10 +1226,13 @@ github.com/viant/xlsy v0.3.1 h1:KwA7PxOTVg+ns4CCPOdfNy5aEA9OUlIByUbuNC9ju0s=
github.com/viant/xlsy v0.3.1/go.mod h1:RajfF9HkL/PfIxRCvZSubpNlpdMUNDKYZp8C1o3vF4Q=
github.com/viant/xmlify v0.1.1 h1:Kmn7wnsq5APD8uJVP+kM6lIEGhSyjWSNOy4BvyfZQno=
github.com/viant/xmlify v0.1.1/go.mod h1:w25+umH6nthlQ8ACT3K2/YJOLlbTXKLQXkdqFs6ky9s=

github.com/viant/xunsafe v0.10.4-0.20260223225257-275a15956559 h1:tQOsy7ov3XcTj+OXNF1apq9EKxSj82f5AjJCuhfCkMo=
github.com/viant/xunsafe v0.10.4-0.20260223225257-275a15956559/go.mod h1:RLSFNYewiF4p7+Lc18N4Zv4DHPWMTEky2VCLWvBdC5o=

github.com/viant/xreflect v0.7.5-0.20260314170600-13f09f37d46e h1:z4uCWPkSCnGwqbIc3ENoYJnYwtR2j/9eI79vO4vK9rQ=
github.com/viant/xreflect v0.7.5-0.20260314170600-13f09f37d46e/go.mod h1:BwI+lqFjhKv2Vn4E0Jt6nvbwcFOWrM6H+sOMOX3JiU4=
github.com/viant/xunsafe v0.10.3 h1:Fi4N+b5PH7e2iwT1UquAe7wUlTn4Fnb2kBnFLBixX+M=
github.com/viant/xunsafe v0.10.3/go.mod h1:V3RCwtqpbNPznhmHysyAOpsyuSVkIYWo1Ewip7qb9/s=

github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg=
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
Expand Down
17 changes: 10 additions & 7 deletions internal/translator/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package translator
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"strings"

"github.com/viant/afs"
"github.com/viant/afs/url"
"github.com/viant/datly/gateway/router"
Expand All @@ -19,10 +24,6 @@ import (
"github.com/viant/datly/shared"
"github.com/viant/datly/view"
"github.com/viant/datly/view/state"
"os"
"path"
"path/filepath"
"strings"
)

type (
Expand Down Expand Up @@ -325,13 +326,15 @@ func (r *Rule) applyDefaults() {
setter.SetCaseFormatIfEmpty(&r.Route.Output.CaseFormat, "lc")
setter.SetBoolIfFalse(&r.Input.IgnoreEmptyQueryParameters, r.IgnoreEmptyQueryParameters)
setter.SetBoolIfFalse(&r.Input.CustomValidation, r.CustomValidation || r.Type != "")
setter.SetStringIfEmpty(&r.Route.Content.Marshaller.JSON.Engine, content.DefaultJSONEngineTypeName)
if r.XMLUnmarshalType != "" {
r.Route.Content.Marshaller.XML.TypeName = r.XMLUnmarshalType
}
if r.JSONMarshalType != "" {
r.Route.Content.Marshaller.JSON.TypeName = r.JSONMarshalType
} else if r.JSONUnmarshalType != "" {
r.Route.Content.Marshaller.JSON.TypeName = r.JSONUnmarshalType
r.Route.Content.Marshaller.JSON.MarshalTypeName = r.JSONMarshalType
}
if r.JSONUnmarshalType != "" {
r.Route.Content.Marshaller.JSON.UnmarshalTypeName = r.JSONUnmarshalType
}
}

Expand Down
8 changes: 4 additions & 4 deletions internal/translator/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,11 +377,11 @@ func (s *Service) persistRouterRule(ctx context.Context, resource *Resource, ser
if resource.Rule.XMLUnmarshalType != "" {
route.Content.Marshaller.XML.TypeName = resource.Rule.XMLUnmarshalType
}
// JSON marshaller/unmarshaller customization: prefer MarshalType if provided, fallback to UnmarshalType.
if resource.Rule.JSONMarshalType != "" {
route.Content.Marshaller.JSON.TypeName = resource.Rule.JSONMarshalType
} else if resource.Rule.JSONUnmarshalType != "" {
route.Content.Marshaller.JSON.TypeName = resource.Rule.JSONUnmarshalType
route.Content.Marshaller.JSON.MarshalTypeName = resource.Rule.JSONMarshalType
}
if resource.Rule.JSONUnmarshalType != "" {
route.Content.Marshaller.JSON.UnmarshalTypeName = resource.Rule.JSONUnmarshalType
}
route.Component.Output.DataFormat = resource.Rule.DataFormat

Expand Down
12 changes: 6 additions & 6 deletions repository/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,8 @@ func (c *Component) Init(ctx context.Context, resource *view.Resource) (err erro
if err := c.initTransforms(ctx); err != nil {
return nil
}
if err := c.Content.InitMarshaller(c.IOConfig(), c.Output.Exclude, c.BodyType(), c.OutputType()); err != nil {
return err
}
lookupType := resource.LookupType()
if err := c.Content.Marshaller.Init(lookupType); err != nil {
if err := c.Content.InitMarshaller(c.IOConfig(), c.Output.Exclude, c.BodyType(), c.OutputType(), lookupType); err != nil {
return err
}
if err = c.Async.Init(ctx, resource, c.View); err != nil {
Expand Down Expand Up @@ -450,10 +447,13 @@ func (c *Component) UnmarshalFor(opts ...UnmarshalOption) shared.Unmarshal {
}
}
return func(data []byte, dest interface{}) error {
if c.Content.Marshaller.JSON.CanUnmarshal() {
return c.Content.Marshaller.JSON.Unmarshal(data, dest)
}
if len(interceptors) > 0 || req != nil {
return c.Content.Marshaller.JSON.JsonMarshaller.Unmarshal(data, dest, interceptors, req)
return c.Content.Marshaller.JSON.RuntimeUnmarshallerEngine().Unmarshal(data, dest, interceptors, req)
}
return c.Content.Marshaller.JSON.JsonMarshaller.Unmarshal(data, dest)
return c.Content.Marshaller.JSON.RuntimeUnmarshallerEngine().Unmarshal(data, dest)
}
}

Expand Down
96 changes: 85 additions & 11 deletions repository/content/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package content

import (
"fmt"
"reflect"
"strings"

"github.com/viant/datly/gateway/router/marshal"
"github.com/viant/datly/gateway/router/marshal/config"
"github.com/viant/datly/gateway/router/marshal/json"
Expand All @@ -12,8 +15,6 @@ import (
"github.com/viant/xlsy"
"github.com/viant/xmlify"
"github.com/viant/xreflect"
"reflect"
"strings"
)

const (
Expand Down Expand Up @@ -59,8 +60,14 @@ type (
}

JSON struct {
Codec
JsonMarshaller *json.Marshaller
Engine string `json:",omitempty" yaml:",omitempty"`
MarshalTypeName string `json:",omitempty" yaml:",omitempty"`
UnmarshalTypeName string `json:",omitempty" yaml:",omitempty"`
marshalCodec Codec `json:"-" yaml:"-"`
unmarshalCodec Codec `json:"-" yaml:"-"`
JsonMarshaller *json.Marshaller `json:"-" yaml:"-"`
RuntimeMarshaller JSONMarshallerEngine `json:"-" yaml:"-"`
RuntimeUnmarshaller JSONUnmarshallerEngine `json:"-" yaml:"-"`
}

XLS struct {
Expand All @@ -86,6 +93,14 @@ type (
Marshaller interface {
Marshal(src interface{}) ([]byte, error)
}

JSONMarshallerEngine interface {
Marshal(src interface{}, options ...interface{}) ([]byte, error)
}

JSONUnmarshallerEngine interface {
Unmarshal(bytes []byte, dest interface{}, options ...interface{}) error
}
)

func (u *Codec) CanUnmarshal() bool {
Expand Down Expand Up @@ -117,7 +132,7 @@ func (m *Marshallers) Init(lookupType xreflect.LookupType) error {
if err := m.JSON.Init(lookupType); err != nil {
return err
}
if err := m.XML.Init(lookupType); err != nil {
if err := m.XML.init(lookupType, false, true); err != nil {
return err
}
if err := m.CSV.Init(lookupType); err != nil {
Expand All @@ -127,6 +142,10 @@ func (m *Marshallers) Init(lookupType xreflect.LookupType) error {
}

func (u *Codec) Init(lookupType xreflect.LookupType) error {
return u.init(lookupType, true, true)
}

func (u *Codec) init(lookupType xreflect.LookupType, requireMarshal, requireUnmarshal bool) error {
if u.TypeName == "" {
return nil
}
Expand All @@ -144,12 +163,57 @@ func (u *Codec) Init(lookupType xreflect.LookupType) error {
if ok {
u.marshal = marshaller.Marshal
}
if u.marshal == nil && u.unmarshal == nil {
return fmt.Errorf("invalid type %s: unmarshaller/marshaller were not initialized", u.TypeName)
if requireMarshal && u.marshal == nil {
return fmt.Errorf("invalid type %s: marshaller was not initialized", u.TypeName)
}
if requireUnmarshal && u.unmarshal == nil {
return fmt.Errorf("invalid type %s: unmarshaller was not initialized", u.TypeName)
}
return nil
}

func (j *JSON) Init(lookupType xreflect.LookupType) error {
j.marshalCodec = Codec{TypeName: j.MarshalTypeName}
j.unmarshalCodec = Codec{TypeName: j.UnmarshalTypeName}
if err := j.marshalCodec.init(lookupType, true, false); err != nil {
return err
}
if err := j.unmarshalCodec.init(lookupType, false, true); err != nil {
return err
}
return nil
}

func (j *JSON) CanMarshal() bool {
return j.marshalCodec.CanMarshal()
}

func (j *JSON) CanUnmarshal() bool {
return j.unmarshalCodec.CanUnmarshal()
}

func (j *JSON) Marshal(src interface{}) ([]byte, error) {
return j.marshalCodec.Marshal(src)
}

func (j *JSON) Unmarshal(bytes []byte, dest interface{}) error {
return j.unmarshalCodec.Unmarshal(bytes, dest)
}

func (j *JSON) RuntimeMarshallerEngine() JSONMarshallerEngine {
if j.RuntimeMarshaller != nil {
return j.RuntimeMarshaller
}
return j.JsonMarshaller
}

func (j *JSON) RuntimeUnmarshallerEngine() JSONUnmarshallerEngine {
if j.RuntimeUnmarshaller != nil {
return j.RuntimeUnmarshaller
}
return j.JsonMarshaller
}

func (c *Content) UnmarshallerInterceptors() marshal.Transforms {
return c.unmarshallerInterceptors
}
Expand All @@ -176,9 +240,19 @@ func (x *XLSConfig) Options() []xlsy.Option {
return options
}

func (c *Content) InitMarshaller(config *config.IOConfig, exclude []string, inputType, outputType reflect.Type) error {
func (c *Content) InitMarshaller(config *config.IOConfig, exclude []string, inputType, outputType reflect.Type, lookupType xreflect.LookupType) error {
c.unmarshallerInterceptors = c.Transforms.FilterByKind(marshal.TransformKindUnmarshal)
c.Marshaller.JSON.JsonMarshaller = json.New(config)
if err := c.Marshaller.Init(lookupType); err != nil {
return err
}
legacyMarshaller := json.New(config)
c.Marshaller.JSON.JsonMarshaller = legacyMarshaller
runtimeMarshaller, runtimeUnmarshaller, err := newJSONMarshaller(config, c.Marshaller.JSON.Engine, legacyMarshaller, lookupType)
if err != nil {
return err
}
c.Marshaller.JSON.RuntimeMarshaller = runtimeMarshaller
c.Marshaller.JSON.RuntimeUnmarshaller = runtimeUnmarshaller
c.Marshaller.XLS.XlsMarshaller = xlsy.NewMarshaller(c.XLS.Options()...)

if err := c.initCSVIfNeeded(inputType, outputType); err != nil {
Expand Down Expand Up @@ -439,14 +513,14 @@ func (c *Content) Marshal(format string, field string, response interface{}, opt
if field != "" {
responseData := ensureSliceValue(response)
tabJSONInterceptors := c.tabJSONInterceptors(field, responseData)
return c.Marshaller.JSON.JsonMarshaller.Marshal(response, tabJSONInterceptors)
return c.Marshaller.JSON.RuntimeMarshallerEngine().Marshal(response, tabJSONInterceptors)
}
return c.TabularJSON.OutputMarshaller.Marshal(response, options...)
case JSONFormat:
if c.Marshaller.JSON.CanMarshal() {
return c.Marshaller.JSON.Marshal(response)
}
return c.Marshaller.JSON.JsonMarshaller.Marshal(response, options...)
return c.Marshaller.JSON.RuntimeMarshallerEngine().Marshal(response, options...)
default:
return nil, fmt.Errorf("unsupproted readerData format: %s", format)
}
Expand Down
Loading
Loading