diff --git a/pkg/items/item.go b/pkg/items/item.go
index 92ef2494981c12e7e28e05c474ceb0db09e3c8be..d04cf999d44198d9a9d2eb5d9725a5aaa957a0f9 100644
--- a/pkg/items/item.go
+++ b/pkg/items/item.go
@@ -13,6 +13,7 @@ import (
 	"git.perx.ru/perxis/perxis-go/pkg/schema/localizer"
 	"git.perx.ru/perxis/perxis-go/pkg/schema/walk"
 	pb "git.perx.ru/perxis/perxis-go/proto/items"
+	"github.com/mitchellh/mapstructure"
 	"google.golang.org/protobuf/types/known/structpb"
 	"google.golang.org/protobuf/types/known/timestamppb"
 )
@@ -106,7 +107,7 @@ type Item struct {
 
 	// При создании или обновлении идентификатор локали в котором создается запись, опционально.
 	// Если указан, то создается перевод для указанного языка, поле translations игнорируется
-	LocaleID string `json:"locale_id" bson:"-"`
+	LocaleID string `json:"localeId,omitempty" bson:"-"`
 
 	// Позволяет одновременно установить/получить несколько переводов и производить манипуляции с переводами
 	// Ключами является идентификатор локали, значениями - данные переводы
@@ -117,11 +118,11 @@ type Item struct {
 	// - {"lang":map{...}} - установка перевода для языка
 	// - {"lang":map{...}, "*":nil} - установка перевода для языка, сброс остальных переводов
 	// - {"*":nil} - сброс всех переводов
-	Translations map[string]map[string]interface{} `json:"translations" bson:"translations,omitempty"`
+	Translations map[string]map[string]interface{} `json:"translations,omitempty" bson:"translations,omitempty"`
 
 	// Список идентификаторов локалей, для которых есть переводы.
 	// Соответствует ключам в translations
-	TranslationsIDs []string `json:"translations_ids" bson:"translations_ids,omitempty"`
+	TranslationsIDs []string `json:"translationsIds,omitempty" bson:"translations_ids,omitempty"`
 
 	RevisionID          string       `json:"revId,omitempty" bson:"revision_id"`
 	RevisionDescription string       `json:"revDescription,omitempty" bson:"revision_description"`
@@ -130,9 +131,9 @@ type Item struct {
 	// Релеватность элемента при полнотекстовом поиске
 	SearchScore float64 `json:"searchScore,omitempty" bson:"search_score,omitempty"`
 
-	Deleted  bool `json:"deleted" bson:"deleted,omitempty"`
-	Hidden   bool `json:"hidden" bson:"hidden,omitempty"`
-	Template bool `json:"template" bson:"template,omitempty"`
+	Deleted  bool `json:"deleted,omitempty"  bson:"deleted,omitempty"`
+	Hidden   bool `json:"hidden,omitempty"   bson:"hidden,omitempty"`
+	Template bool `json:"template,omitempty" bson:"template,omitempty"`
 }
 
 func NewItem(spaceID, envID, collID, id string, data map[string]interface{}, translations map[string]map[string]interface{}) *Item {
@@ -169,6 +170,8 @@ func (i *Item) Clone() *Item {
 	return &itm
 }
 
+// ToMap конвертирует текущий элемент в map[string]any.
+// DEPRECATED, используйте ToMap.
 func (i *Item) ToMap() map[string]interface{} {
 	return map[string]interface{}{
 		"id":                   i.ID,
@@ -194,6 +197,86 @@ func (i *Item) ToMap() map[string]interface{} {
 	}
 }
 
+// ToMap конвертирует переданный Item в map[string]any, кодируя данные согласно схеме.
+// Вычисляемые поля удаляются из результата.
+func ToMap(item *Item, sch *schema.Schema) (map[string]any, error) {
+	if item == nil {
+		return nil, errors.New("item must not be nil")
+	}
+	if sch == nil {
+		return nil, errors.New("schema must not be nil")
+	}
+
+	output := make(map[string]any)
+
+	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
+		TagName: "json",
+		Result:  &output,
+	})
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to create decoder")
+	}
+
+	item, err = item.Encode(context.Background(), sch)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to encode item by schema")
+	}
+
+	err = decoder.Decode(item)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to encode item")
+	}
+
+	// Кодируем системные поля со временем
+	output["createdRevAt"] = item.CreatedRevAt.Format(time.RFC3339)
+	output["createdAt"] = item.CreatedAt.Format(time.RFC3339)
+	output["updatedAt"] = item.UpdatedAt.Format(time.RFC3339)
+
+	// Удаляем вычисляемые поля
+	delete(output, "permissions")
+	delete(output, "searchScore")
+
+	return output, nil
+}
+
+// FromMap конвертирует переданный map[string]any в Item, декодируя данные согласно схеме.
+// Вычисляемые поля игнорируются при декодировании.
+func FromMap(input map[string]any, sch *schema.Schema) (*Item, error) {
+	if len(input) == 0 {
+		return nil, errors.New("input map must not be empty or nil")
+	}
+	if sch == nil {
+		return nil, errors.New("schema must not be nil")
+	}
+
+	item := &Item{}
+
+	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
+		DecodeHook: mapstructure.StringToTimeHookFunc(time.RFC3339),
+		TagName:    "json",
+		Result:     item,
+	})
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to create decoder")
+	}
+
+	// Удаляем вычисляемые поля
+	delete(input, "permissions")
+	delete(input, "searchScore")
+
+	err = decoder.Decode(input)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to decode")
+	}
+
+	item, err = item.Decode(context.Background(), sch)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to decode item by schema")
+	}
+
+	return item, nil
+}
+
 // SetData устанавливает перевод в нужное поле записи
 func (i *Item) SetData(dt map[string]interface{}, localeID string) {
 	if localeID == "" || localeID == locales.DefaultID {
diff --git a/pkg/items/item_test.go b/pkg/items/item_test.go
index 1344444e685ee025e9b7e13a4bc2d781eb36a9de..734883bb5ea2f88f40bb9b8c40f577c2fedd656a 100644
--- a/pkg/items/item_test.go
+++ b/pkg/items/item_test.go
@@ -506,3 +506,243 @@ func Test_mergeItemData(t *testing.T) {
 		})
 	}
 }
+
+func TestToMap(t *testing.T) {
+	sch := schema.New(
+		"str", field.String(),
+		"num", field.Number(field.NumberFormatFloat),
+		"embedded", field.Object(
+			"now", field.Time(),
+		),
+		"timestamp", field.Timestamp(),
+	)
+
+	tests := []struct {
+		name      string
+		input     *Item
+		sch       *schema.Schema
+		want      map[string]any
+		assertErr assert.ErrorAssertionFunc
+	}{
+		{
+			name:      "nil item",
+			input:     nil,
+			sch:       nil,
+			want:      nil,
+			assertErr: assert.Error,
+		},
+		{
+			name:      "nil schema",
+			input:     &Item{},
+			sch:       nil,
+			want:      nil,
+			assertErr: assert.Error,
+		},
+		{
+			name: "simple",
+			input: &Item{
+				ID:           "item_id",
+				SpaceID:      "space_id",
+				EnvID:        "env_id",
+				CollectionID: "coll_id",
+				State:        StatePublished,
+				CreatedRevAt: time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
+				CreatedBy:    "created_by",
+				CreatedAt:    time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
+				UpdatedAt:    time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
+				UpdatedBy:    "updated_by",
+				Data: map[string]any{
+					"str": "string",
+					"num": 2.7,
+					"embedded": map[string]any{
+						"now": time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
+					},
+					"timestamp": 1723420800,
+				},
+				LocaleID: "locale_id",
+				Translations: map[string]map[string]any{
+					"ru": {
+						"str": "строка",
+						"num": 3.14,
+					},
+				},
+				TranslationsIDs:     []string{"ru"},
+				RevisionID:          "rev_id",
+				RevisionDescription: "rev_desc",
+				Permissions: &Permissions{
+					Edit:       true,
+					Archive:    false,
+					Publish:    true,
+					SoftDelete: false,
+					HardDelete: false,
+				},
+				SearchScore: 123.0,
+				Deleted:     false,
+				Hidden:      true,
+				Template:    true,
+			},
+			sch: sch,
+			want: map[string]any{
+				"id":           "item_id",
+				"spaceId":      "space_id",
+				"envId":        "env_id",
+				"collectionId": "coll_id",
+				"state":        StatePublished,
+				"createdRevAt": "2024-08-12T00:00:00Z",
+				"createdBy":    "created_by",
+				"createdAt":    "2024-08-12T00:00:00Z",
+				"updatedAt":    "2024-08-12T00:00:00Z",
+				"updatedBy":    "updated_by",
+				"data": map[string]any{
+					"str": "string",
+					"num": 2.7,
+					"embedded": map[string]any{
+						"now": "2024-08-12T00:00:00Z",
+					},
+					"timestamp": int64(1723420800),
+				},
+				"localeId": "locale_id",
+				"translations": map[string]map[string]any{
+					"ru": {
+						"str": "строка",
+						"num": 3.14,
+					},
+				},
+				"translationsIds": []string{"ru"},
+				"revId":           "rev_id",
+				"revDescription":  "rev_desc",
+				"hidden":          true,
+				"template":        true,
+			},
+			assertErr: assert.NoError,
+		},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			got, err := ToMap(tc.input, tc.sch)
+			tc.assertErr(t, err)
+			assert.Equal(t, tc.want, got)
+		})
+	}
+}
+
+func TestFromMap(t *testing.T) {
+	sch := schema.New(
+		"str", field.String(),
+		"num", field.Number(field.NumberFormatFloat),
+		"embedded", field.Object(
+			"now", field.Time(),
+		),
+		"timestamp", field.Timestamp(),
+	)
+
+	tests := []struct {
+		name      string
+		input     map[string]any
+		want      *Item
+		sch       *schema.Schema
+		assertErr assert.ErrorAssertionFunc
+	}{
+		{
+			name:      "nil item",
+			input:     nil,
+			sch:       nil,
+			want:      nil,
+			assertErr: assert.Error,
+		},
+		{
+			name:      "empty item",
+			input:     map[string]any{},
+			sch:       nil,
+			want:      nil,
+			assertErr: assert.Error,
+		},
+		{
+			name:      "nil schema",
+			input:     map[string]any{},
+			sch:       nil,
+			assertErr: assert.Error,
+		},
+		{
+			name: "simple",
+			input: map[string]any{
+				"id":           "item_id",
+				"spaceId":      "space_id",
+				"envId":        "env_id",
+				"collectionId": "coll_id",
+				"state":        StatePublished,
+				"createdRevAt": "2024-08-12T00:00:00Z",
+				"createdBy":    "created_by",
+				"createdAt":    "2024-08-12T00:00:00Z",
+				"updatedAt":    "2024-08-12T00:00:00Z",
+				"updatedBy":    "updated_by",
+				"data": map[string]any{
+					"str": "string",
+					"num": 2.7,
+					"embedded": map[string]any{
+						"now": "2024-08-12T00:00:00Z",
+					},
+					"timestamp": 1723420800,
+				},
+				"localeId": "locale_id",
+				"translations": map[string]map[string]any{
+					"ru": {
+						"str": "строка",
+						"num": 3.14,
+					},
+				},
+				"translationsIds": []string{"ru"},
+				"revId":           "rev_id",
+				"revDescription":  "rev_desc",
+				"deleted":         false,
+				"hidden":          true,
+				"template":        true,
+			},
+			sch: sch,
+			want: &Item{
+				ID:           "item_id",
+				SpaceID:      "space_id",
+				EnvID:        "env_id",
+				CollectionID: "coll_id",
+				State:        StatePublished,
+				CreatedRevAt: time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
+				CreatedBy:    "created_by",
+				CreatedAt:    time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
+				UpdatedAt:    time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
+				UpdatedBy:    "updated_by",
+				Data: map[string]any{
+					"str": "string",
+					"num": 2.7,
+					"embedded": map[string]any{
+						"now": time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
+					},
+					"timestamp": int64(1723420800),
+				},
+				LocaleID: "locale_id",
+				Translations: map[string]map[string]any{
+					"ru": {
+						"str": "строка",
+						"num": 3.14,
+					},
+				},
+				TranslationsIDs:     []string{"ru"},
+				RevisionID:          "rev_id",
+				RevisionDescription: "rev_desc",
+				Permissions:         nil,
+				SearchScore:         0.0,
+				Deleted:             false,
+				Hidden:              true,
+				Template:            true,
+			},
+			assertErr: assert.NoError,
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			got, err := FromMap(tc.input, sch)
+			tc.assertErr(t, err)
+			assert.Equal(t, tc.want, got)
+		})
+	}
+}