This question already has answers here:
How to deep copy a map and then clear the original?
(6 answers)
Closed 4 years ago.
What am I trying to do?
Copy a "default" struct into a new one when needed, keeping all it's values.
Details
I am trying to copy a Chat struct:
type ChatData struct {
User map[string]map[string]string `json:"user"`
Chat map[string]string `json:"chat"`
}
type Chat struct {
Settings map[string]map[string]interface{} `json:"settings"`
Data ChatData `json:"data"`
}
I only need to do this when a new chat is introduced, and I check for membership in a map[string]Chat.
//data is a map[string]Chat
if _, exists := data[event.Chat]; !exists {
data[event.Chat] = data["default"]
}
The full default struct is:
{
"default": {
"settings": {
"eightball": {
"messages": [
"yes",
"no",
"maybe"
]
},
"main": {
"blacklistedCommands": [],
"blacklistedUsers": [],
"error": "there was an error - ",
"maxConsecutive": 5,
"permissions": "You don't have permissions for that command.",
"success": "The command was successful.",
"whitelistedCommands": [],
"whitelistedUsers": []
}
},
"data": {
"user": {
"default": {
"admin": "false",
"consecutiveCommands": "0",
"nickname": "",
"sentMessages": "0"
},
"total": {
"admin": "false",
"consecutiveCommands": "0",
"nickname": "",
"sentMessages": "0"
}
},
"chat": {
"commandSender": "",
"lastMessage": "",
"lastSender": "",
"lastTimestamp": "",
"wasCommand":""
}
}
}
}
What have I tried
data[event.Chat] = data["default"]
// causes a reference
data[event.Chat] = &data["default"]
// cannot use &data["default"] (type *Chat) as type Chat in assignment
data[event.Chat] = *data["default"]
// invalid indirect of data["default"] (type Chat)
Do I need to change my use of map[string]Chat to map[string]*Chat and go with the second option? Pointers and references are not my specialty, so help would be appreciated.
Edit
whoever thought I was copying a map, what are you smoking?
I have found in previous cases, that an easy (though not the most efficient) way to copy a complex structure is to Marshal it, and then Unmarshal it into a new variable. This can be done with a variety of encodings. Two easy ones (included in the stdlib) would be json and gob.
There are also plenty of libraries using reflection to achieve a similar goal: https://github.com/jinzhu/copier
But like I said, though not as efficient, this is easy to reason about and takes just a single, simple function to achieve. If performance matters you should prefer gob over json. If performance REALLY matters, then you should prefer another solution altogether.
If you know the data structure it is best for you to fully define your structs. It will be much easier to manage than complex maps and interfaces with reflection to cast data types. Here is a tool to help you convert JSON to Go structs. It isn't perfect and you have to confirm the data types. You may even want to rip out the internal structs and create types for them for readability and maintenance, but it will save you some time.
https://mholt.github.io/json-to-go/
package main
import (
"encoding/json"
"fmt"
"log"
)
var rawDataExample = []byte(`{
"default": {
"settings": {
"eightball": {
"messages": [
"yes",
"no",
"maybe"
]
},
"main": {
"blacklistedCommands": [],
"blacklistedUsers": [],
"error": "there was an error - ",
"maxConsecutive": 5,
"permissions": "You don't have permissions for that command.",
"success": "The command was successful.",
"whitelistedCommands": [],
"whitelistedUsers": []
}
},
"data": {
"user": {
"default": {
"admin": "false",
"consecutiveCommands": "0",
"nickname": "",
"sentMessages": "0"
},
"total": {
"admin": "false",
"consecutiveCommands": "0",
"nickname": "",
"sentMessages": "0"
}
},
"chat": {
"commandSender": "",
"lastMessage": "",
"lastSender": "",
"lastTimestamp": "",
"wasCommand":""
}
}
}
}
`)
type Settings struct {
Default struct {
Settings struct {
Eightball struct {
Messages []string `json:"messages"`
} `json:"eightball"`
Main struct {
BlacklistedCommands []string `json:"blacklistedCommands"`
BlacklistedUsers []string `json:"blacklistedUsers"`
Error string `json:"error"`
MaxConsecutive int `json:"maxConsecutive"`
Permissions string `json:"permissions"`
Success string `json:"success"`
WhitelistedCommands []string `json:"whitelistedCommands"`
WhitelistedUsers []string `json:"whitelistedUsers"`
} `json:"main"`
} `json:"settings"`
Data struct {
User struct {
Default struct {
Admin string `json:"admin"`
ConsecutiveCommands string `json:"consecutiveCommands"`
Nickname string `json:"nickname"`
SentMessages string `json:"sentMessages"`
} `json:"default"`
Total struct {
Admin string `json:"admin"`
ConsecutiveCommands string `json:"consecutiveCommands"`
Nickname string `json:"nickname"`
SentMessages string `json:"sentMessages"`
} `json:"total"`
} `json:"user"`
Chat struct {
CommandSender string `json:"commandSender"`
LastMessage string `json:"lastMessage"`
LastSender string `json:"lastSender"`
LastTimestamp string `json:"lastTimestamp"`
WasCommand string `json:"wasCommand"`
} `json:"chat"`
} `json:"data"`
} `json:"default"`
}
type Data struct {
Events map[string]Settings
}
func main() {
var settings Settings
err := json.Unmarshal(rawDataExample, &settings)
if err != nil {
log.Fatal(err)
}
event := "foo"
d := Data{
Events: make(map[string]Settings),
}
if _, ok := d.Events[event]; !ok {
// event doesn't exist
// add it
d.Events[event] = settings
fmt.Println("event added")
}
if _, ok := d.Events[event]; !ok {
// event exist, this will never be happen
// sanity check
fmt.Println("this will never be printed")
}
fmt.Printf("%+v\n", d)
}
Super simple fix, just wanted to get some input before I did it. Changed the map[string]Chat > map[string]*Chat and made a new *Chat, then copied the data.
data[event.Chat] = &Chat{}
*data[event.Chat] = *data["default"]
Related
I'm having trouble validating a schema in Postman using tv4 inside the tests tab - it is always returning a true test, no matter what I feed it. I am at a complete loss and could really use a hand - here is my example JSON Response, and my tests:
I've tried a ton of variations from every Stack Overflow/tutorial I could find and nothing will work - it always returns true.
//Test Example
var jsonData = JSON.parse(responseBody);
const schema = {
"required" : ["categories"],
"properties": {
"categories": {
"required" : ["aStringOne", "aStringTwo", "aStringThree" ],
"type": "array",
"properties" : {
"aStringOne": {"type": "string" },
"aStringTwo": {"type": "null" },
"aStringThree": {"type": "boolean" }
}
}
}
};
pm.test('Schema is present and accurate', () => {
var result=tv4.validateMultiple(jsonData, schema);
console.log(result);
pm.expect(result.valid).to.be.true;
});
//Response Example
{
"categories": [
{
"aStringOne": "31000",
"aStringTwo": "Yarp",
"aStringThree": "More Yarp Indeed"
}
]
}
This should return false, as all three properties are strings but its passing. I'm willing to use a different validator or another technique as long as I can export it as a postman collection to use with newman in my CI/CD process. I look forward to any help you can give.
I would suggest moving away from using tv4 in Postman, the project isn't actively supported and Postman now includes a better (in my opinion), more actively maintained option called Ajv.
The syntax is slightly different but hopefully, this gives you an idea of how it could work for you.
I've mocked out your data and just added everything into the Tests tab - If you change the jsonData variable to pm.response.json() it will run against the actual response body.
var jsonData = {
"categories": [
{
"aStringOne": "31000",
"aStringTwo": "Yarp",
"aStringThree": "More Yarp Indeed"
}
]
}
var Ajv = require('ajv'),
ajv = new Ajv({logger: console, allErrors: true}),
schema = {
"type": "object",
"required": [ "categories"],
"properties": {
"categories": {
"type": "array",
"items": {
"type": "object",
"required": [ "aStringOne", "aStringTwo", "aStringThree" ],
"properties": {
"aStringOne": { "type": "string" },
"aStringTwo": { "type": "integer"},
"aStringThree": { "type": "boolean"},
}
}
}
}
}
pm.test('Schema is valid', function() {
pm.expect(ajv.validate(schema, jsonData), JSON.stringify(ajv.errors)).to.be.true
});
This is an example of it failing, I've included the allErrors flag so that it will return all the errors rather than just the first one it sees. In the pm.expect() method, I've added JSON.stringify(ajv.errors) so you can see the error in the Test Result tab. It's a little bit messy and could be tidied up but all the error information is there.
Setting the properties to string show the validation passing:
If one of the required Keys is not there, it will also error for this too:
Working with schemas is quite difficult and it's not easy to both create them (nested arrays and objects are tricky) and ensure they are doing what you want to do.
There are occasions where I thought something should fail and it passed the validation test. It just takes a bit of learning/practising and once you understand the schema structures, they can become extremely useful.
I can't seem to find how to correctly call PutItem for a StringSet in DynamoDB through API Gateway. If I call it like I would for a List of Maps, then I get objects returned. Example data is below.
{
"eventId": "Lorem",
"eventName": "Lorem",
"companies": [
{
"companyId": "Lorem",
"companyName": "Lorem"
}
],
"eventTags": [
"Lorem",
"Lorem"
]
}
And my example template call for companies:
"companies" : {
"L": [
#foreach($elem in $inputRoot.companies) {
"M": {
"companyId": {
"S": "$elem.companyId"
},
"companyName": {
"S": "$elem.companyName"
}
}
} #if($foreach.hasNext),#end
#end
]
}
I've tried to call it with String Set listed, but it errors out still and tells me that "Start of structure or map found where not expected" or that serialization failed.
"eventTags" : {
"SS": [
#foreach($elem in $inputRoot.eventTags) {
"S":"$elem"
} #if($foreach.hasNext),#end
#end
]
}
What is the proper way to call PutItem for converting an array of strings to a String Set?
If you are using JavaScript AWS SDK, you can use document client API (docClient.createSet) to store the SET data type.
docClient.createSet - converts the array into SET data type
var docClient = new AWS.DynamoDB.DocumentClient();
var params = {
TableName:table,
Item:{
"yearkey": year,
"title": title
"product" : docClient.createSet(['milk','veg'])
}
};
I want to save all data of a form.
My form has these elements-
( Using Postman Plugin )
My controller is like this-
[HttpPost]
public async Task<IActionResult> Insert(IFormCollection data)
{
return Ok(data);
}
So, I am getting something like this-
[
{
"key": "user_id",
"value": [
"'12'"
]
},
{
"key": "title",
"value": [
"123"
]
},
{
"key": "text[]",
"value": [
"werwer",
"ghj"
]
}
]
I want to get the value of texts.
So, for this case-
"werwer",
"ghj"
So, I have tried something like this-
foreach (string description in data["text"])
{
// description => empty
}
and also tried this-
data.text
and also-
data->text
But nothing works for me.
Can anyone please help?
Thanks in advance for helping.
Why not loop through each keys and if the key is "text", get the values. Since the value is a comma seperated string, you can call the Split method on that to get an array which contains 2 items( from your sample input).
foreach (string description in data.Keys)
{
if (description.Equals("text"))
{
var v = data[description];
var stringItems = v.Split(',');
foreach (var stringItem in stringItems)
{
//do something with stringItem
}
}
}
BTW, the key should be text, not text[]. Even if you have muliple input fields with the same name "text", when you submit, It will be a single key ("text") with 2 items int he value property
{
"key": "text",
"value": [
"werwer",
"ghj"
]
}
Given the two JTokens:
{ "Users": { "Name": "Carl" } }
and
{ "Users": [ { "Name": "Carl" }, {"Name": "Peter"} ] }
How can I tell if Users is a JProperty or JObject/JArray?
I need loop Users with
foreach (JObject User in myjobject["Users"]) { ... }
Solution
It was as simple as myjobject["Users"].GetType(). However, that didn't work in the Watch debugger window, but it worked at runtime. Hrmpff.
The Type property will tell you the type of the token you have.
switch(token.Type)
{
case JTokenType.Array:
break;
case JTokenType.String:
break;
}
I was making a function to convert all dates in an object to strings and when I used the following function I got a error in FF "too much recursion". (It also fails in IE and chrome)
function datesToString(obj) {
if (obj instanceof Object) {
if (obj instanceof Date) {
obj = "this part does not matter";
} else {
for (var key in obj) { obj[key] = datesToString(obj[key]); }
}
}
return obj;
}
but when I changed it to use typeof it worked fine?
function datesToString(obj) {
if (obj && typeof obj == "object") {
if (obj instanceof Date) {
obj = "this part does not matter";
} else {
for (var key in obj) { obj[key] = datesToString(obj[key]); }
}
}
return obj;
}
It should be the same amount of recursion. Am I missing something? Why am I getting an error with the first example but not the second?
Update:
here is an example of the object I was using (in json form)
Contact = {
"LContactID": "00000000-0000-0000-0000-000000000000",
"CellPhone": "",
"HomePhone": "4564564560",
"OtherPhone": "",
"BirthDate": new Date(-62135575200000),
"SSN": "456456456",
"HowToContact": 1,
"ContactType": 3,
"Address1": "123 test",
"Address2": "apt. 1",
"City": "Valparaiso",
"State": "IN",
"Zip": "46383",
"FullAddress": "123 test, apt. 1",
"BuilderID": "",
"FullAddressWithCityState": "123 test\r\napt. 1\r\nValparaiso, IN 46383",
"WorkHours": "9-5",
"ContactTime": "",
"ContactMethod": "???",
"IsMilitaryOrSelfEmployed": false,
"AlternateContactName": "",
"AlternateContactPhone": "",
"ContactID": "00000000-0000-0000-0000-000000000000",
"Password": null,
"FirstName": "updatetest",
"MiddleName": null,
"LastName": "test_",
"Suffix": null,
"EmailAddress": null,
"EmailAddressAlternate": null,
"WorkPhone": "6546546540",
"WorkPhoneExt": null,
"CreatedOn": new Date(1263597309000),
"CreatedOnOverride": new Date(-62135575200000),
"ModifiedOn": new Date(1264014749000),
"SCRep": "",
"HBCRep": "",
"PPPRep": "",
"DPSRep": "",
"DRSRep": "",
"RDRep": "",
"OwnerID": "00000000-0000-0000-0000-000000000000",
"FullName": "updatetest test_",
"ImportID": 0,
"IncludeEmail": false,
"PreferredLanguage": 1,
"CarbonCopyList": [],
"HearAboutUs": 5,
"HearAboutUsOther": "",
"init": function() { },
"update": function() { },
"load": function() { }
}
It looks like when I remove the method parameters (init,update and load) it works with the instanceof example.
UPDATE: This is a ASP.Net ScriptManager problem it turns out. Sorry for not including the fact that I was working with an ASP.Net website.The ASP.Net ScriptManager prototypes methods to Function which causes an infinity loop when doing a recursive for in loop on a function.
(First, let me say that I tried your code on FF 3.5.7 and it didn't run into an infinite recursion. maybe you used a different input example?)
Anyway, to answer your questions:
(a) Why do you get an infinite recursion? Because an object may refer to itself. If o.f==o then calling your function on o will fire a subsequent call on o.f which is actually o and so on...
(b) Why the difference between the two versions? obj instanceof Object checks if obj is an instance of Object or an instance of a subclass thereof. Given that every Javascript object inherits from Object then this condition is trivially true, and is thus meaningless. Net result is that the first if always succeeds. On the other hand typeof obj == "object" checks that obj is an instance of Object. This may be false, for example, if obj is an instance of String (at which case typeof obj yields String). Hence, the difference.