I have a scenario where I have to convert the entire struct into a map.
I know we have a library structs.Map(s) which will convert the struct to a map. But I want to know is there a way where i can convert multiple struct inside struct to map[string]interface.
for example we have below
package main
import (
"log"
"github.com/fatih/structs"
)
type Community struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Sources []Source `json:"sources,omitempty"`
Moderators []string `json:"moderators,omitempty"`
}
type Source struct {
SourceName string `json:"sourceName,omitempty"`
Region []State `json:"region,omitempty"`
}
type State struct {
State1 string `json:"state1,omitempty"`
State2 string `json:"state2,omitempty"`
}
func main() {
compareData := Community{
Name: "A",
Description: "this belong to A community",
Sources: []Source{
{
SourceName: "SourceA",
Region: []State{
{
State1: "State1",
},
{
State2: "State2",
},
},
},
},
}
m := structs.Map(compareData)
log.Println(m)
}
this will give result as below ,that is it is creating map for the inside struct again
map[Description:this belong to A community
Moderators:[]
Name:A Sources:[map[SourceName:SourceA Region:[map[State1:State1 State2:] map[State1: State2:State2]]]]]
my expectation is get only a single map[string]interface{}
map[
Description:this belong to A community
Moderators:[]
Name:A
SourceName:SourceA
State1:State1
State2:State2
]
my purpose of creating a single map is to compare the value to a different map based on key .
My struct also varies as per the different response so i want to have a map where i can get all the key value pairs for easy comparison . If someone has a suggestion to this please let me know .
You can use mapstructure package.
Sample of usage :
package main
import (
"fmt"
"github.com/mitchellh/mapstructure"
)
func main() {
type Emails struct {
Mail []string
}
type Person struct {
Name string
Age int
Emails Emails
Extra map[string]string
}
// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the
// struct initially.
mails := []string{"foo#bar.com", "foo2#bar.com"}
input := Person{
Name: "foo",
Age: 25,
Emails: Emails{Mail: mails},
Extra: map[string]string{"family": "bar"},
}
result := map[string]interface{}{}
err := mapstructure.Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
}
playground
Related
Ok so I have a REST API in GO which stores a ticket resource using firestore. For this I use: firestore go client
I want to be able to order my documents by date created / date updated, so by following the docs I store these 2 fields as timestamps in the document.
I use the tag serverTimestamp on these 2 fields. By doing this, the value should be the time at which the firestore server processed the request.
The HTTP response of the update operation should have this body:
{
"ticket": {
"id": "af41766e-76ea-43b5-86c1-8ba382edd4dc",
"title": "Ticket updated title",
"price": 9,
"date_created": "2023-01-06 09:07:24",
"date_updated": "2023-01-06 10:08:24"
}
}
So it means after I update the ticket document, besides an updated title or price I also need to have the updated value fo the date_updated field.
This is working for the moment but I'm curious if the way I coded this is the way to do it. As you can see in the code samples, I use a transaction to update a ticket. I didn't find a way to retrieve the updated value for the DateUpdated field, other than reading again the updated ticket.
The domain entity is defined as this:
package tixer
import (
"context"
"time"
"github.com/google/uuid"
)
type (
// TicketID represents a unique identifier for a ticket.
// It's a domain type.
TicketID uuid.UUID
// Ticket represents an individual ticket in the system.
// It's a domain type.
Ticket struct {
ID TicketID
Title string
Price float64
DateCreated time.Time
DateUpdated time.Time
}
)
I'll attach here the communication with firestore from the create and update perspective:
// Storer persists tickets in Firestore.
type Storer struct {
client *firestore.Client
}
func NewStorer(client *firestore.Client) *Storer {
return &Storer{client}
}
func (s *Storer) CreateTicket(ctx context.Context, ticket *tixer.Ticket) error {
writeRes, err := s.client.Collection("tickets").Doc(ticket.ID.String()).Set(ctx, createTicket{
Title: ticket.Title,
Price: ticket.Price,
})
// In this case writeRes.UpdateTime is the time the document was created.
ticket.DateCreated = writeRes.UpdateTime
return err
}
func (s *Storer) UpdateTicket(ctx context.Context, ticket *tixer.Ticket) error {
docRef := s.client.Collection("tickets").Doc(ticket.ID.String())
err := s.client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
doc, err := tx.Get(docRef)
if err != nil {
switch {
case status.Code(err) == codes.NotFound:
return tixer.ErrTicketNotFound
default:
return err
}
}
var t persistedTicket
if err := doc.DataTo(&t); err != nil {
return err
}
t.ID = doc.Ref.ID
if ticket.Title != "" {
t.Title = ticket.Title
}
if ticket.Price != 0 {
t.Price = ticket.Price
}
return tx.Set(docRef, updateTicket{
Title: t.Title,
Price: t.Price,
DateCreated: t.DateCreated,
})
})
if err != nil {
return err
}
updatedTicket, err := s.readTicket(ctx, ticket.ID)
if err != nil {
return err
}
*ticket = updatedTicket
return nil
}
func (s *Storer) readTicket(ctx context.Context, id tixer.TicketID) (tixer.Ticket, error) {
doc, err := s.client.Collection("tickets").Doc(id.String()).Get(ctx)
if err != nil {
switch {
case status.Code(err) == codes.NotFound:
return tixer.Ticket{}, tixer.ErrTicketNotFound
default:
return tixer.Ticket{}, err
}
}
var t persistedTicket
if err := doc.DataTo(&t); err != nil {
return tixer.Ticket{}, err
}
t.ID = doc.Ref.ID
return toDomainTicket(t), nil
}
type (
// persistedTicket represents a stored ticket in Firestore.
persistedTicket struct {
ID string `firestore:"id"`
Title string `firestore:"title"`
Price float64 `firestore:"price"`
DateCreated time.Time `firestore:"dateCreated"`
DateUpdated time.Time `firestore:"dateUpdate"`
}
// createTicket contains the data needed to create a Ticket in Firestore.
createTicket struct {
Title string `firestore:"title"`
Price float64 `firestore:"price"`
DateCreated time.Time `firestore:"dateCreated,serverTimestamp"`
DateUpdated time.Time `firestore:"dateUpdate,serverTimestamp"`
}
// updateTicket contains the data needed to update a Ticket in Firestore.
updateTicket struct {
Title string `firestore:"title"`
Price float64 `firestore:"price"`
DateCreated time.Time `firestore:"dateCreated"`
DateUpdated time.Time `firestore:"dateUpdate,serverTimestamp"`
}
)
func toDomainTicket(t persistedTicket) tixer.Ticket {
return tixer.Ticket{
ID: tixer.TicketID(uuid.MustParse(t.ID)),
Title: t.Title,
Price: t.Price,
DateCreated: t.DateCreated,
DateUpdated: t.DateUpdated,
}
}
If I understand correctly, the DateUpdated field is a server-side timestamp, which means that its value is determined by the server (as a so-called field transformation) when the value is written to the storage layer. Since a write operation in the Firestore SDK doesn't return the resulting data of that operation, the only way to get that value back into your application is indeed to perform an extra read operation after the write to get it.
The SDK doesn't automatically perform this read is because it is a charged operation, which in many cases is not needed. So by leaving it up to your code to perform that read, you can decide whether to incur this cost or not.
I am trying to add a nested struct to Firestore and for some reason the contents added are all non-structs, which look something like:
The structs look something like this:
type Status struct {
Title string `json:"title,omitempty" firestore:"title,omitempty"`
Message string `json:"message,omitempty" firestore:"title,omitempty"`
}
type Config struct {
Name string `json:"name,omitempty" firestore:"name,omitempty"`
Status Status `json:"status,omitempty" firestore:"status,omitempty"`
}
And the code looks something like this:
import (
"context"
firebase "firebase.google.com/go/v4"
"google.golang.org/api/option"
)
func main() {
configuration := Config{
Name: "Test",
Status: Status{
Title: "hello",
Message: "hi",
},
}
ctx := context.Background()
config := firebase.Config{
ProjectID: "",
StorageBucket: "",
}
opt := option.WithCredentialsFile("firebase_config.json")
app, err := firebase.NewApp(ctx, &config, opt)
if err != nil {
panic(err)
}
// Get an auth client from the firebase.App
client, err := app.Firestore(ctx)
if err != nil {
panic(err)
}
_, _, err = client.Collection("forecast").Add(ctx, configuration)
if err != nil {
panic(err)
}
}
The above code only works for elements that are not in the nested structure.
Any help on this would be appreciated
Update 1
Status is not a sub collection but an object, something like:
{
"name": "Test",
"status": {
"title": "hello",
"message": "hi"
}
}
Firestore is optimized as a hash entry and retrieval datastore. As a consequence, it's better to create maps out of your structs. Structs are good for Go data modeling but when it's time to submit to the database, convert it to a map.
I usually just use Fatih's struct to map converter
It makes it easy to reason about your data on the Go side and still be able to submit it for storage.
Posting this as Community Wiki answer, based in the discussion of the comments.
The solution for this case seems to be adding values manually in a field of type Map. The steps to achieve that are the following: Access the Firebase console -> Firestore -> Create a document -> Add field of type Map. Following this order it's possible to create a field of type Map, that has the format needed to add data as described in the description of the question.
More information about this type, how it works, sort options, etc., can be found in the official documentation here: Supported data types.
Using Swift code 5.1 I have managed to update Firestore Database with items in current users basket but not able to add/update quantity. Currently if I wanted to add an item that already exist in the basket it simply adds another line but I wanted to just update quantity.
Can you advise me on how to create a function that adds quantity?
Here are the codes I have so far. Only relevant sections of code pasted.
Firestore DB function in my Helper file:
enum FCollectionReference: String {
case User
case Category
case Items
case Basket
case Orders
}
func FirebaseReference(_ collectionReference: FCollectionReference) -> CollectionReference {
return Firestore.firestore().collection(collectionReference.rawValue)
}
Here's the code in in my Basket Model file using
class Basket {
var id: String!
var ownerId: String!
var itemIds: [String]!
var delivery: Float!
var admin: Float!
var quantity: Int!
init() {
}
init(_dictionary: NSDictionary) {
id = _dictionary[kOBJECTID] as? String
ownerId = _dictionary[kOWNERID] as? String
itemIds = _dictionary[kITEMIDS] as? [String]
delivery = _dictionary[kDELIVERY] as? Float
admin = _dictionary[kADMIN] as? Float
quantity = _dictionary[kQUANTITY] as? Int
}
}
//MARK: Helper functions
func basketDictionaryFrom(_ basket: Basket) -> NSDictionary {
return NSDictionary(objects: [basket.id, basket.ownerId, basket.itemIds, basket.quantity], forKeys: [kOBJECTID as NSCopying, kOWNERID as NSCopying, kITEMIDS as NSCopying, kQUANTITY as NSCopying,kDELIVERY as NSCopying, kADMIN as NSCopying])
}
//MARK: - Update basket
func updateBasketInFirestore(_ basket: Basket, withValues: [String : Any], completion: #escaping (_ error: Error?) -> Void) {
FirebaseReference(.Basket).document(basket.id).updateData(withValues) { (error) in
completion(error)
Codes in Item View Control to add items to basket:
#objc func addToBasketButtonPressed() {
//check if user is logged in or show login view
if MUser.currentUser() != nil {
downloadBasketFromFirestore(MUser.currentId()) { (basket) in
if basket == nil {
self.createNewBasket()
}else {
basket?.itemIds.append(self.item.id)
self.updateBasket(basket: basket!, withValues: [kITEMIDS: basket!.itemIds])
}
}
} else {
showLoginView()
}
}
private func updateBasket(basket: Basket, withValues: [String : Any]) {
updateBasketInFirestore(basket, withValues: withValues) { (error) in
if error != nil {
self.hud.textLabel.text = "Error: \(error!.localizedDescription)"
self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
self.hud.show(in: self.view)
self.hud.dismiss(afterDelay: 2.0)
print("error updating basket", error!.localizedDescription)
}else {
self.hud.textLabel.text = "Added to Basket"
self.hud.indicatorView = JGProgressHUDSuccessIndicatorView()
self.hud.show(in: self.view)
self.hud.dismiss(afterDelay: 2.0)
}
}
}
To clarify my request, what do I need to change/re-arrange in my coding so the Database Cloud Firestore is arranged in order shown in my attached screen shot. First screen shot showing current layout in the last column and I'm trying to change this to layout demonstrated in the second screen shot?
I think you are asking how to update the value in a field within a Firestore document. If not, let me know and I will update the answer.
Here's some code that updates the qty of an item in inventory. Pass in the qty to add as a + Int and then to subtract as a - Int. The structure looks like this
root
inventory
item_0
qty: 0
and the code to update the qty node is:
func incrementQty(deltaQty: Int) {
let docToUpdate = self.db.collection("inventory").document("item_0")
docToUpdate.updateData( [
"qty": FieldValue.increment( Int64(deltaQty) )
])
}
call it like this
self.incrementQty(deltaQty: 4) //adds 4 to the existing qty
previously, incrementing values had to be wrapped into a transaction to make it safe but the FieldValue makes it much easier.
I am adding another answer based on comments and question clarification. My other answer still stands as an answer but it's a different approach.
Arrays are inherently hard to work with in NoSQL databases as they are often treated as a single object. They have limited functionality opposed to collections, documents and fields, and can't directly be sorted or have items inserted. And querying is well, challenging. Firestore does a great job at providing better interoperability with arrays but there are still usually better options.
Instead of an array, I would change the structure to this:
Baskets (collection)
basket_number (document in the Baskets collection, like you have now)
items //a collection of items in the basket
item_0 //a document with the docID being the the item number
item_qty: //qty of the item
item_1
item_qty:
item_2
item_qty:
So the downside of .updateData is that if the field being updated doesn't exist, it doesn't create the field, it simply throws an error. So we need to test to see if the document exists first, if so, update with updateData, if not create the item with an initial quantity.
Here's the code that does it - note for simplicity I am ignoring the top level Basket and basket_number since you already know how to do that part and focused on the items collection and down.
func incrementQty(itemNumberToUpdate: String, deltaQty: Int) {
let docToUpdate = self.db.collection("items").document(itemNumberToUpdate)
docToUpdate.getDocument(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
if let _ = documentSnapshot?.data() {
print("item exists, update qty")
docToUpdate.updateData([
"item_qty": FieldValue.increment( Int64(deltaQty) )
], completion: { err in
if let err = err {
print("Error updating document: \(err.localizedDescription)")
} else {
print("Item qty successfully updated")
}
})
} else {
print("no item exists, need to create")
docToUpdate.setData([
"item_qty": FieldValue.increment( Int64(deltaQty) )
], completion: { err in
if let err = err {
print("Error updating document: \(err.localizedDescription)")
} else {
print("Item successfully created with initial quantity")
}
})
}
})
}
Pass in an item number and the quantity to either modify the existing qty by, or will be the initial quantity.
self.incrementQty(itemNumberToUpdate: "item_0", deltaQty: 5)
I want to pretty print the contents of a sync map in Go.
I have a sync.Map data I want to print the contents of it.
To view the value of a specific key SiteData I can run the following code.
var data sync.Map
siteData := map[string]string{"Name": "StackOverflow"}
data.Store("SiteData", siteData)
temp, _ := data.Load("SiteData")
b, _ := json.MarshalIndent(temp, "", " ")
fmt.Println(string(b))
But I wish to print the entire map at once. This is because the data can have many keys and I want to print them at once.
Running the below code doesn't work and prints {}
var data sync.Map
siteData := map[string]string{"Name": "StackOverflow"}
data.Store("SiteData", siteData)
b, _ := json.MarshalIndent(data, "", " ")
fmt.Println(string(b))
Fields (internals) of sync.Map are not exported, so they can't be accessed, and more importantly they can't be accessed without synchronization. So you can't just print the contents of a sync.Map.
What you may do is iterate over all entries of your sync.Map, build an "ordinary" map from it, and display that. Note that the "ordinary" map must have string key type (maps with interface{} key type are not supported by the encoding/json package). We may simply convert interface{} keys to string using fmt.Sprint(). To get all entries, you may use Map.Range().
For example:
var data sync.Map
data.Store("SiteData", map[string]string{
"Name": "StackOverflow",
"Url": "https://so.com",
})
data.Store("Else", "something else")
m := map[string]interface{}{}
data.Range(func(key, value interface{}) bool {
m[fmt.Sprint(key)] = value
return true
})
b, err := json.MarshalIndent(m, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(b))
This will output (try it on the Go Playground):
{
"Else": "something else",
"SiteData": {
"Name": "StackOverflow",
"Url": "https://so.com"
}
}
Here is a simple util function (you may modify base on it as need):
// print map's key/value, with index (not stable),
func PrintSyncMap(m sync.Map) {
// print map,
fmt.Println("map content:")
i := 0
m.Range(func(key, value interface{}) bool {
fmt.Printf("\t[%d] key: %v, value: %v\n", i, key, value)
i++
return true
})
}
Example output:
map content:
[0] key: Qian, value: class 2
[1] key: Zhao, value: class 1
[2] key: Li, value: class 4
Tips:
Index is not stable. (aka. Order of printed item may change among multiple calls.)
This question already has answers here:
Is there a way to get the field names of a struct in a macro?
(3 answers)
Closed 6 years ago.
Is there some equivalent of JS's Object.keys() for Rust's struct?
I need something to generate CSV headers (I use rust-csv) from structure field names.
struct Export {
first_name: String,
last_name: String,
gender: String,
date_of_birth: String,
address: String
}
//... some code
let mut wrtr = Writer::from_file("/home/me/export.csv").unwrap().delimiter(b'\t');
wrtr.encode(/* WHAT TO WRITE HERE TO GET STRUCT NAMES as tuple of strings or somethings */).is_ok()
The current main method of metaprogramming in Rust is via macros. In this case, you can capture all the field names and then add a method that returns string forms of them:
macro_rules! zoom_and_enhance {
(struct $name:ident { $($fname:ident : $ftype:ty),* }) => {
struct $name {
$($fname : $ftype),*
}
impl $name {
fn field_names() -> &'static [&'static str] {
static NAMES: &'static [&'static str] = &[$(stringify!($fname)),*];
NAMES
}
}
}
}
zoom_and_enhance!{
struct Export {
first_name: String,
last_name: String,
gender: String,
date_of_birth: String,
address: String
}
}
fn main() {
println!("{:?}", Export::field_names());
}
For advanced macros, be sure to check out The Little Book of Rust Macros.