Скрыть свойства из JSON - PullRequest
       4

Скрыть свойства из JSON

0 голосов
/ 18 февраля 2019

У меня есть таблица структуры с 2 игроками, но мне нужно игнорировать некоторые свойства из структуры Player при отправке JSON.

Я мог бы использовать json:"-", но тогда свойство будет игнорироватьсяВСЕГДА, и мне нужно игнорировать это только при отправке структуры таблицы.Мне нужны эти свойства, когда я посылаю Player в других частях кода.

У меня есть:

type Player struct {
    Id            Int64   `json:"id"`
    Username      string  `json:"username,omitempty"`
    Password      string          `json:"-,omitempty"`
    Email         string          `json:"email,omitempty"`
    Birthdate     time.Time       `json:"birthdate,omitempty"`
    Avatar        string  `json:avatar,omitempty"`
}

type Table struct {
    Id           int       `json:"id"`
    PlayerTop    Player      `json:"playerTop"`
    PlayerBottom Player      `json:"playerBottom"`
}

Мне нужно:

{
    "Table": {
        "id": 1,
        "playerBottom": {
            "id": 1,
            "username": "peter",
            "avatar": "avatar.png"
        },
        "playerTop": {
            "id": 1,
            "username": "peter",
            "avatar": "avatar.png"
        }

    }
}

Игрокиприходят из базы данных, поэтому свойства не пусты.

a) Я мог бы сделать что-то вроде:

myTable = new(Table)

myTable.PlayerBottom.Email = ""
myTable.PlayerBottom.Birthdate = ""
myTable.PlayerTop.Email = ""
myTable.PlayerTop.Birthdate = ""

, поэтому эти свойства будут игнорироваться в JSON, благодаря json:"omitempty", но это плохая идея.

b) Я мог бы использовать что-то вроде псевдонима структуры, но Table ожидает, что PlayerBottom имеет тип Player, а не PlayerAlias, но я нене знаю, как это реализовать:

type PlayerAlias struct {
    Id            Int64   `json:"id"`
    Username      string  `json:"username,omitempty"`
    Avatar        string  `json:avatar,omitempty"`
}

c) Я пытался динамически добавить json:"-" к свойствам, которые я не хотел от JSON, прежде чем отправлять его, но это был беспорядок.

Ответы [ 5 ]

0 голосов
/ 19 февраля 2019

Если вы хотите представить публичную и приватную версию данных - и одна версия является расширенной версией другой, попробуйте внедренные структуры .При добавлении собственного маршаллера JSON вы можете получить две презентации с одинаковыми основными данными.

Database JSON: {"Id":12345,"PlayerTop":{"id":456,"username":"Peter","avatar":"peter.png","password":"Secr3t","birthdate":"0001-01-01T00:00:00Z"},"PlayerBottom":{"id":890,"username":"Paul","avatar":"paul.png","password":"abc123","birthdate":"0001-01-01T00:00:00Z"}}

Public JSON: {"id":12345,"playerTop":{"id":456,"username":"Peter","avatar":"peter.png"},"playerBottom":{"id":890,"username":"Paul","avatar":"paul.png"}}

Запустить на детская площадка :

// public info
type PublicPlayer struct {
        Id       int64  `json:"id"`
        Username string `json:"username,omitempty"`
        Avatar   string `json:"avatar,omitempty"`
}

// private info
type Player struct {
        PublicPlayer // embed public info

        Password  string    `json:"password,omitempty"`
        Email     string    `json:"email,omitempty"`
        Birthdate time.Time `json:"birthdate,omitempty"`
}

type Table struct {
    Id           int    `json:"id"`
    PlayerTop    Player `json:"playerTop"`
    PlayerBottom Player `json:"playerBottom"`
}

// derivative type, so we can add a custom marshaller
type PublicTable Table

func (t PublicTable) MarshalJSON() ([]byte, error) {
        return json.Marshal(
                // anonymous struct definition
                struct {
                        Id     int          `json:"id"`
                        Top    PublicPlayer `json:"playerTop"`
                        Bottom PublicPlayer `json:"playerBottom"`
                }{  
                        t.Id,
                        t.PlayerTop.PublicPlayer,    // only export public data
                        t.PlayerBottom.PublicPlayer, // only export public data
                },  
        )   
}
0 голосов
/ 18 февраля 2019

Пользовательский маршаллер - отличный способ изменить способ отображения вашего объекта в JSON.Однако в вашем случае я бы не советовал этого делать, если вам когда-нибудь понадобится сопоставить весь ваш объект с JSON в какой-то другой точке (например, для инструмента администратора).

Некоторые ключевые моменты этого ответа:

  • Все значения доступны внутри
  • Из кода Маршалла ясно, что значения будут исключены, и легко получить код, исключающий значения
  • Минимизировать повторение и новыеТипы

Я бы предложил просто определить функцию в вашей структуре, которая возвращает карту полей, которые вы хотите выставить.

Из вашего примера:

type Player struct {
    Id        int64     `json:"id"`
    Username  string    `json:"username,omitempty"`
    Password  string    `json:"-,omitempty"`
    Email     string    `json:"email,omitempty"`
    Birthdate time.Time `json:"birthdate,omitempty"`
    Avatar    string    `json:"avatar,omitempty"`
}

func (p Player) PublicInfo() map[string]interface{} {
    return map[string]interface{}{
        "id":       p.Id,
        "username": p.Username,
        "avatar":   p.Avatar,
    }
}

Существует несколько способов использования этой функции.Один простой способ состоит в том, чтобы Table struct использовал карты для PlayerTop и PlayerBottom:

type Table struct {
    Id           int                         `json:"id"`
    PlayerTop    map[string]interface{}      `json:"playerTop"`
    PlayerBottom map[string]interface{}      `json:"playerBottom"`
}

func NewTable(id int, playerTop, playerBottom Player) Table {
    return Table{Id: id, 
                 PlayerTop: playerTop.PublicInfo(), 
                 PlayerBottom: playerBottom.PublicInfo()}
}

. Выделение этого в JSON вернет нужные поля.И вам нужно всего лишь отредактировать одно место, чтобы добавить / удалить поля из JSON.

Если вы используете тип Table для внутреннего использования и вам необходим доступ к игрокам с него, то вам все равно может понадобиться сохранитьполная Player структура на Table.Я бы просто следовал шаблону Public сверху с таблицей следующим образом:

type Table struct {
    Id           int    `json:"id"`
    PlayerTop    Player `json:"playerTop"`
    PlayerBottom Player `json:"playerBottom"`
}

func (t Table) PublicInfo() map[string]interface{} {
    return map[string]interface{}{
        "id":           t.Id,
        "playerTop":    t.PlayerTop.PublicInfo(),
        "playerBottom": t.PlayerBottom.PublicInfo(),
    }
}

Теперь, когда вы создаете таблицу и используете ее для внутреннего использования, ясно, что это за типы, и когда вы упорядочиваете JSON, это ясночто вы исключаете некоторые типы и где это исключение происходит.

func main() {
    p1 := Player{Id: 1, Username: "peter", Avatar: "avatar.png", Email: "PRIVATE"}
    p2 := Player{Id: 1, Username: "peter", Avatar: "avatar.png", Email: "PRIVATE"}
    t := Table{Id: 1, PlayerTop: p1, PlayerBottom: p2}
    admin, _ :=  json.Marshal(t)
    public, _ := json.Marshal(t.PublicInfo())
    fmt.Println(fmt.Sprintf("For admins: %s", string(admin)))
    fmt.Println(fmt.Sprintf("For public: %s", string(public)))
}
/*
Output: 
For admins: {"id":1,"playerTop":{"id":1,"username":"peter","email":"PRIVATE","birthdate":"0001-01-01T00:00:00Z","avatar":"avatar.png"},"playerBottom":{"id":1,"username":"peter","email":"PRIVATE","birthdate":"0001-01-01T00:00:00Z","avatar":"avatar.png"}}
For public: {"id":1,"playerBottom":{"avatar":"avatar.png","id":1,"username":"peter"},"playerTop":{"avatar":"avatar.png","id":1,"username":"peter"}}
*/

Смотрите это в действии: https://play.golang.org/p/24t-B6ZuUKu

0 голосов
/ 18 февраля 2019

Есть несколько способов добиться этого.Первым было бы создать собственный маршаллер для типа Table.Это, однако, несколько утомительно и может быть довольно ограничительным.ИМХО, есть более простой способ сделать то же самое: embed types:

type PartialPlayer struct {
     Player // embed the entire type
     Email string `json:"-"` // override fields and add the tag to exclude them
     Birthdate string `json:"-"`
}

Теперь вы можете получить доступ ко всем нужным данным и даже добавить геттеры для косвенного доступа к данным:

func (pp PartialPlayer) GetEmail() string {
    if pp.Email == "" {
        return pp.Player.Email // get embedded Email value
    }
    return pp.Email // add override value
}

Обратите внимание, что вам не нужно , чтобы использовать эти функции получения.Поле Id не переопределяется, поэтому, если у меня есть переменная PartialPlayer, я могу получить доступ к значению напрямую:

pp := PartialPlayer{
    Player: playerVar,
}
fmt.Printf("Player ID: %v\n", pp.Id) // still works

Вы можете получить доступ к переопределенным / замаскированным полям, указав, что вы хотите, чтобы значение удерживалосьвстроенный тип, также без функции:

fmt.Printf("Email on partial: '%s', but I can see '%s'\n", pp.Email, pp.Player.Email)

Последний напечатает Email on partial: '', but I can see 'foo@bar.com'.

Используйте этот тип в Table, например:

type Table struct {
    Id           int            `json:"id"`
    PlayerTop    PartialPlayer  `json:"playerTop"`
    PlayerBottom PartialPlayer  `json:"playerBottom"`
}

Инициализация:

tbl := Table{
    Id: 213,
    PlayerTop: PartialPlayer{
        Player: playerVar,
    },
    PlayerBottom: PartialPlayer{
        Player: player2Var,
    },
}

Это прекрасно работает.Преимущество этого подхода состоит в том, что для маршалинга в JSON и из него не требуется вызов ваших пользовательских функций маршаллера, а также создание / отображение промежуточных типов, таких как карты или скрытые типы и т. Д. *

Если вы хотите скрытьдругое поле, просто добавьте его к типу PartialPlayer.Если вы хотите отобразить поле типа Email, просто удалите его из типа PartialPlayer, работа выполнена.


Теперь для подхода с пользовательским маршаллером:

type Table struct {
    Id           int    `json:"id"`
    PlayerTop    Player `json:"playerTop"`
    PlayerBottom Player `json:"playerBottom"`
}

type marshalTable {
    Table
    // assuming the PartialPlayer type above
    PlayerTop    PartialPlayer `json:"playerTop"`
    PlayerBottom PartialPlayer `json:"playerBottom"`
}

func (t Table) MarshalJSON() ([]byte, error) {
    mt := marshalTable{
        Table:        t,
        PlayerTop:    PartialPlayer{
            Player: t.PlayerTop,
        },
        PlayerBottom: PartialPlayer{
            Player: t.PlayerBottom,
        },
    }
    return json.Marshal(mt)
}

Это не слишком отличается от построения типа map[string]interface{} здесь, но при использовании встраивания типа вам не нужно обновлять функцию маршаллера каждый раз, когда поле переименовывается или изменяется в типе Player.

Используя этот подход, ваш тип Table может использоваться точно так же, как вы делаете это сейчас, но вывод JSON не будет включать поля Email и Birthdate.

0 голосов
/ 18 февраля 2019

Типы, отличающиеся только тегами полей, преобразуются друг в друга с версии 1.8 .Таким образом, вы можете определить один или несколько типов «просмотра» для игроков и выбрать тот, который соответствует вашему сценарию использования при маршалинге.

Преимущество по сравнению с внедрением или реализацией json.Marshaler заключается в том, что каждый раз, когда выдобавьте новое поле к Player, компилятор также заставляет вас обновлять каждый тип представления, т. е. вы должны сознательно решить, включать ли новое поле в каждое представление.

package main

import (
    "encoding/json"
    "fmt"
    "time"
)

type Player struct {
    Id        int64     `json:"id"`
    Username  string    `json:"username,omitempty"`
    Password  string    `json:"-,omitempty"`
    Email     string    `json:"email,omitempty"`
    Birthdate time.Time `json:"birthdate,omitempty"`
    Avatar    string    `json:"avatar,omitempty"`
}

// PlayerSummary has the same underlying type as Player, but omits some fields 
// in the JSON representation.
type PlayerSummary struct {
    Id        int64     `json:"id"`
    Username  string    `json:"username,omitempty"`
    Password  string    `json:"-"`
    Email     string    `json:"-"`
    Birthdate time.Time `json:"-"`
    Avatar    string    `json:"avatar,omitempty"`
}

type Table struct {
    Id           int           `json:"id"`
    PlayerTop    PlayerSummary `json:"playerTop"`
    PlayerBottom PlayerSummary `json:"playerBottom"`
}

func main() {
    p1 := Player{
        Id:        1,
        Username:  "Alice",
        Email:     "alice@example.com",
        Birthdate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
        Avatar:    "https://www.gravatar.com/avatar/c160f8cc69a4f0bf2b0362752353d060",
    }
    p2 := Player{
        Id:        2,
        Username:  "Bob",
        Email:     "bob@example.com",
        Birthdate: time.Date(1998, 6, 1, 0, 0, 0, 0, time.UTC),
        Avatar:    "https://www.gravatar.com/avatar/4b9bb80620f03eb3719e0a061c14283d",
    }

    b, _ := json.MarshalIndent(Table{
        Id:           0,
        PlayerTop:    PlayerSummary(p1), // marshal p1 as PlayerSummary
        PlayerBottom: PlayerSummary(p2), // marshal p2 as PlayerSummary
    }, "", "  ")

    fmt.Println(string(b))
}

// Output:
// {
//   "id": 0,
//   "playerTop": {
//     "id": 1,
//     "username": "Alice",
//     "avatar": "https://www.gravatar.com/avatar/c160f8cc69a4f0bf2b0362752353d060"
//   },
//   "playerBottom": {
//     "id": 2,
//     "username": "Bob",
//     "avatar": "https://www.gravatar.com/avatar/4b9bb80620f03eb3719e0a061c14283d"
//   }
// }

Попробуйтена игровой площадке: https://play.golang.org/p/a9V2uvOJX3Y


В стороне: рассмотрите возможность удаления поля пароля из Player.Пароль (хэш) обычно используется только очень немногими функциями.Функции, которые действительно нуждаются в этом, могут принимать игрока и пароль в качестве отдельных аргументов.Таким образом вы исключите риск случайной утечки пароля (например, в сообщениях журнала).

0 голосов
/ 18 февраля 2019

Вы можете создать пользовательский Marshaler для Table типов.Это интерфейс, который вы должны реализовать:

https://golang.org/pkg/encoding/json/#Marshaler

type Marshaler interface {
        MarshalJSON() ([]byte, error)
}

Затем вы удалите тег - из Player (потому что, когда вы собираете его в другом месте, вам нужносохранить поля) и игнорировать его только в пользовательском методе MarshalJSON Table.


Вот простой (не связанный) пример реализации пользовательского маршалинга для типа, кодирующего одно из полейв гексах:

type Account struct {
    Id   int32
    Name string
}

func (a Account) MarshalJSON() ([]byte, error) {
    m := map[string]string{
        "id":   fmt.Sprintf("0x%08x", a.Id),
        "name": a.Name,
    }
    return json.Marshal(m)
}

func main() {
    joe := Account{Id: 123, Name: "Joe"}
    fmt.Println(joe)

    s, _ := json.Marshal(joe)
    fmt.Println(string(s))
}

Как вы можете видеть здесь, такой маршалинг легко сделать, построив map только с необходимыми полями и передав его в json.Marshal.Для ваших Table и Player это приведет всего к нескольким строкам тривиального кода.ИМХО, лучше сделать это, чем модифицировать типы и усложнить их встраиванием / псевдонимами, просто для кодирования JSON.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...