How do I pass through values from an Elasticsearch document in Go? - json

I'm experimenting with rewriting parts of our system in Go. They're currently written in Python. Some of the data that I want to serve lives in Elasticsearch.
Across our users, we have a few standard fields, but also allow people to create a number of custom fields specific to their environment. E.g., we have a product object that has some common fields like name and price, but we let someone create a field like discount_price or requires_freight to agree with their use case.
In Python, this is easy to accommodate. The JSON is read in, our chosen JSON parser does some reasonable type inference, and we can then return the data after it's processed.
In Go, any data we want to deal with from the Elasticsearch JSON response has to be mapped to a type. Or at least that's my understanding. For example:
import (
"encoding/json"
)
...
type Product struct {
Name string `json:"name"`
Price string `json:"price"`
}
Here's a simplified example of what the data might look like. I've prefixed the names of the nonstandard fields I'd want to pass through with custom:
{
"id": "ABC123",
"name": "Great Product",
"price": 10.99,
"custom_alternate_names": ["Great Product"],
"custom_sellers": [{
"id": "ABC123",
"name": "Great Product LLC",
"emails": ["great#stuff.com"]
}]
}
It would be fine to special case for places where the route actually needs to process or manipulate a custom field. But in most cases, passing through the JSON data unchanged is fine, so the fact we aren't imposing any type mappings for safety isn't adding anything.
Is there a way to set up the struct with an interface (?) that could act as a passthrough for any unmapped fields? Or a way to take the unparsed JSON data and recombine it with the mapped object before the data are returned?

You can do something like this
package main
import (
"encoding/json"
"log"
)
type Product struct {
//embed product to be able to pull the properties without
//requiring a nested object in the json
KnownFields
OtherStuff json.RawMessage
}
//Custom unmarshaller
func (p *Product) UnmarshalJSON(b []byte) error {
var k KnownFields
//unmarshal the known fields
err := json.Unmarshal(b, &k)
if err != nil {
return err
}
p.KnownFields = k
//You can use json.RawMessage or map[string]interface
p.OtherStuff = json.RawMessage(b)
return nil
}
type KnownFields struct {
Name string `json:"name"`
Price json.Number `json:"price"`
}
const JSON = `
{
"id": "ABC123",
"name": "Great Product",
"price": 10.99,
"custom_alternate_names": ["Great Product"],
"custom_sellers": [{
"id": "ABC123",
"name": "Great Product LLC",
"emails": ["great#stuff.com"]
}]
}`
func main() {
var p Product
err := json.Unmarshal([]byte(JSON), &p)
if err != nil {
log.Panic(err)
}
log.Printf("%v", p)
}
If you are going to mutate and marshall the Product you will have to implement a custom marshaller and also you would need to use map[string]interface{} as the OtherStuff to avoid having duplicate entries for the known fields

Related

Error: interface conversion interface {} is []interface {}, not map[string]interface {}

I am building a project that takes a term from the user and then performs a google search and returns a list of titles in json format.
I am using the serpwow API to perform the google search and am trying to parse the response.
However I am getting the error that states:
panic: interface conversion: interface {} is []interface {}, not map[string]interface {}.
I have looked through various forms and have tried to learn how mapping works but I am not sure why in this case, my mapping is not working. The table for organic results looks like this:
"organic_results": [
{
"position": 1,
"title": "The 10 Best Pizza Places in Dublin - TripAdvisor",
"link": "https://www.tripadvisor.ie/Restaurants-g186605-c31-Dublin_County_Dublin.html",
"domain": "www.tripadvisor.ie",
"displayed_link": "https://www.tripadvisor.ie › ... › County Dublin › Dublin",
"snippet": "Best Pizza in Dublin, County Dublin: Find TripAdvisor traveller reviews of Dublin Pizza places and search by price, location, and more.",
"prerender": false,
"snippet_matched": [
"Pizza",
"Pizza"
],
"cached_page_link": "https://webcache.googleusercontent.com/search?q=cache:OS-Ar9hB_ngJ:https://www.tripadvisor.ie/Restaurants-g186605-c31-Dublin_County_Dublin.html+&cd=4&hl=en&ct=clnk&gl=ie",
"related_page_link": "https://www.google.com/search?q=related:https://www.tripadvisor.ie/Restaurants-g186605-c31-Dublin_County_Dublin.html+pizza&tbo=1&sa=X&ved=2ahUKEwicjYKvvNjmAhVoSBUIHa9MBhcQHzADegQIARAH",
"block_position": 2
},
and here is a snip of my code:
package main
import (
"fmt"
"strings"
serpwow "github.com/serpwow/google-search-results-golang"
)
func main() {
// set API key
apiKey := "Key_Hidden"
//read term to search
fmt.Print("What term would you like to search in google? ")
var term string
fmt.Scanln(&term)
// set up our search parameters
parameters := map[string]interface{}{
"q": term,
}
// retrieve the search results as JSON
response, error := serpwow.GetJSON(parameters, apiKey)
// print the response, or error, if one occurred
if error != nil {
fmt.Println(error)
} else {
//extract title from organic results
//result := fmt.Sprintf("%v", response["organic_results"].(map[string]interface{})["title"])
for _, item := range response["organic_results"].([]interface{}) {
fmt.Sprintf("%v", item.(map[string]interface{})["title"])
}
//s := []string{result, "\n"}
//fmt.Printf(strings.Join(s, " "))
}
}
Can someone please help me figure our where my logic is wrong?
response["organic_results"] corresponds to the JSON array "organic_results", hence it is not a map[string]interface{}, but a []interface. There are multiple results, not one.
for _,item:=range respose["organic_results"].([]interface{}) {
fmt.Printf("%v", item.(map[string]interface{})["title"])
}
As you can see in your JSON, organic_results's value is a JSON array, not an object; each element of the array is an object. So you can't assert organic_results to map[string]interface{} because, as the error states, it's not a map, it's a []interface. You could, for example, do:
result := fmt.Sprintf("%v", response["organic_results"].([]interface{})[0].(map[string]interface{})["title"])
But of course this will only get you the title of the first result, and it will crash if the results are empty. You'll have to think of this not as "get the title from the response" and instead as "handling zero or more results returned" - i.e. you'll probably want to loop over organic_results and do something with each result object.
Sometimes, reverse engineering also works, I had the same issue of not able to UnMarshall a custom made JSON file although all the types which I had defined looked very intuitive.
type ResourceConfig struct {
ResourceType map[string]AlphabetType `json:"resourceType"`
RedisConfig RedisConfigT `json:"redisConfig"`
}
type AlphabetType struct {
XXX []string `json:"xxx"`
YYY []string `json:"yyy"`
}
type RedisConfigT struct {
Broker string `json:"broker"`
DB string `json:"db"`
}
For which the json looked like this:
{
"resourceType": [
{"/abcdefg": [{"xxx": ["/vrf", "/lsp", "/vpn"], "yyy": ["/fpc"]}]},
{"/vrf": [{"xxx": [""], "yyy": ["/abcdefg"]}]},
{"/lsp": [{"xxx": [""], "yyy": ["/abcdefg"]}]},
{"/vpn": [{"xxx": [""], "yyy": ["/abcdefg"]}]},
{"/fpc": [{"xxx": ["/abcdefg"], "yyy": [""]}]}
],
"redisConfig": {"broker": "localhost: 6379", "db": 0}
}
When doing UnMarshall it was throwing error of not able to parse.
So I decided to form the required map programmatically first Marshal it into a json, and print it.
{"resourceType":{"/fpc":{"XXX":["/abcdefg"],"YYY":[]},
"/abcdefg":{"XXX":["/vrf","/lsp","/vpn"],"YYY":["/fpc"]},
"/abc":{"XXX":[],"YYY":["/abcdefg"]},
"/vpn":{"XXX":[],"YYY":["/abcdefg"]},
"/vrf":{"XXX":[],"YYY":["/abcdefg"]}},
"redisConfig":{"broker":"localhost:6349","db":"0"}}
Now use the same format in. the json file and UnMarshal it. It will nicely
fit into the types you have defined based on which the Json was generated originally.

Can one run a PutItem in DynamoDB in Golang without using a struct?

All of AWS' examples involve using a struct to organize and optimize your data prior to doing anything with DynamoDB. See: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/dynamo-example-create-table-item.html
For my project I am getting a JSON string back from a third party API. I actually want to store the entire JSON object in DynamoDB but I can't determine if it's possible without a struct. Because I'm working with a third party API, I can't guarantee what fields I will get, or if the fields will change in the future. Can this be done? In NodeJS it's quite easy, but I understand that Go is statically typed so it may just not be possible.
You will always need a struct for your item static key and/or range. The JSON can go in another field of type map, which can contain items or nested maps. Then let attributevalue.MarshalMap transform the whole struct into a DynamoDB map.
type Record struct {
ID string
JSON map[string]interface{}
}
r := Record{
ID: "ABC123",
JSON: map[string]interface{}{
"Parent": "aa",
"Children": map[string]interface{}{
"Child1": "foo",
"Child2": "bar",
},
},
}
av, err := attributevalue.MarshalMap(r)
if err != nil {
return fmt.Errorf("failed to marshal Record, %w", err)
}
_, err = client.PutItem(context.TODO(), &dynamodb.PutItemInput{
TableName: aws.String(myTableName),
Item: av,
})
if err != nil {
return fmt.Errorf("failed to put Record, %w", err)
}
example partly taken from https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue

Unmarshal Inconsistent JSON in Go

I'm working with JSON that returns three different object types 'items','categories' and 'modifiers'. An example of the JSON can be viewed here. I created models for the three types of objects. But when I unmarshal I have selected one of the types to unmarshal the entire JSON to.(I know this cant be the correct way...) I then try to parse out the different items depending on what their type is identified as in the json field 'Type' and then append that object to a slice of the proper type. I am having errors because I don't know how to unmarshal JSON that has different types in it that have different fields.
What is the proper method to unmarshal JSON that contains different objects, each with their own respective fields?
Is the solution to create a "super model" which contains all possible fields and then unmarshal to that?
I'm still fairly new and would appreciate any advice. Thanks!
If you implement json.Unmarshaler, you can define a struct that parses each item type into it's relevant struct.
Example:
// Dynamic represents an item of any type.
type Dynamic struct {
Value interface{}
}
// UnmarshalJSON is called by the json package when we ask it to
// parse something into Dynamic.
func (d *Dynamic) UnmarshalJSON(data []byte) error {
// Parse only the "type" field first.
var meta struct {
Type string
}
if err := json.Unmarshal(data, &meta); err != nil {
return err
}
// Determine which struct to unmarshal into according to "type".
switch meta.Type {
case "product":
d.Value = &Product{}
case "post":
d.Value = &Post{}
default:
return fmt.Errorf("%q is an invalid item type", meta.Type)
}
return json.Unmarshal(data, d.Value)
}
// Product and Post are structs representing two different item types.
type Product struct {
Name string
Price int
}
type Post struct {
Title string
Content string
}
Usage:
func main() {
// Parse a JSON item into Dynamic.
input := `{
"type": "product",
"name": "iPhone",
"price": 1000
}`
var dynamic Dynamic
if err := json.Unmarshal([]byte(input), &dynamic); err != nil {
log.Fatal(err)
}
// Type switch on dynamic.Value to get the parsed struct.
// See https://tour.golang.org/methods/16
switch dynamic.Value.(type) {
case *Product:
log.Println("got a product:", dynamic.Value)
case *Post:
log.Println("got a product:", dynamic.Value)
}
}
Output:
2009/11/10 23:00:00 got a product: &{iPhone 1000}
Try it in the Go Playground.
Tip: if you have a list of dynamic objects, just parse into a slice of Dynamic:
var items []Dynamic
json.Unmarshal(`[{...}, {...}]`, &items)
Example output:
[&{iPhone 1000} &{A Good Post Lorem ipsum...}]
I think https://github.com/mitchellh/mapstructure also fits into your use case.

Parse structs from API

I am trying to access some information stored in a json file via Go. I have two related issues. One is that I'm not sure how to organize my structs and secondly how do I access them via a variable. I'll notate my code to make a little more sense
// To be clear, this is dummy info and I'm linting my actual json
// data. It loads fine, I just don't want to get hung up on this side
{
"A": {
"lob": "A",
"url": [
"example.com",
"test.com"]
}
"B": {
"lob": "B",
"url": [
"example2.com",
"test2.com"]
}
}
So the concern is that the structure of the options is identical. I am building this as part of a REST AP. The hope is that users can use http://testapi.com/getdata/A and it will return the urls and name info under A and likewise for B. As is, it loads both of them as separate components of the same struct:
type SiteList struct {
A struct {
Lob string `json:"lob"`
URL []string `json:"url"`
} `json:"test"`
B struct {
Lob string `json:"lob"`
URL []string `json:"url"`
} `json:"test2"`
}
I can do .A or .B by hand but I'm wondering how to handle it when the requests come in so that my API will only return the data under A or B.
If you're going to consume the API via accessing the API via http://testapi.com/getdata/A or http://testapi.com/getdata/B then A and B can be considered the parameters that drive the behavior of your API.
If you're passing A, you basically want to access the site data associated with A and if you're passing B, the site data for B should be returned.
An easy way to organize this data internally is to use a dedicated Go type site which holds Lob and URL and arrange everything in a map via map[string]site, which is initialized on startup of your server.
You can then dynamically access the parameter given to your API (A or B, but can be easily extended), lookup the site information from the map and, in case it's a valid site, return the corresponding data encoded as JSON.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type site struct {
Lob string `json:"lob"`
URL []string `json:"url"`
}
var sites = map[string]site{
"A": site{
Lob: "A",
URL: []string{
"example.com",
"test.com",
},
},
"B": site{
Lob: "B",
URL: []string{
"example2.com",
"test2.com",
},
},
}
const endpoint = "/getdata/"
func handler(w http.ResponseWriter, r *http.Request) {
lob := r.URL.Path[len(endpoint):]
s, ok := sites[lob]
if !ok {
w.WriteHeader(http.StatusNotFound)
return
}
resp, err := json.Marshal(s)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Write(resp)
}
func main() {
http.HandleFunc(endpoint, handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Missing values in the json will be unmarshalled with their zero values. An int will be 0, a string will be "", a map and any pointer will be nil. Structs will be initialized, but their fields will have the zero value.
In your case, when comcast is missing in the json, B will be initialized as a struct, where Lob is "" and URL is an empty slice of string.
If I understand correctly you have a struct SiteList and depending on if they user navigates to /getData/A or /getData/B you want to serve SiteList.A or SiteList.B.
From the json marshal docs here or this SO answer you can leave out some fields of a struct when they are empty. Basically if there's no data in that field then it will not appear in the marshalled json.
The "omitempty" option specifies that the field should be omitted from
the encoding if the field has an empty value, defined as false, 0, a
nil pointer, a nil interface value, and any empty array, slice, map,
or string.
With that in mind, if you have the control in the handlers of the requests to be able to set the values in SiteList according to the path then you could make use of this feature.
If you extract the shared parts of A and B into a struct:
type Inner struct {
Lob string `json:"lob"`
URL []string `json:"url"`
}
// using pointers here for A and B means setting to nil is empty
type SiteList struct {
A *Inner `json:"test,omitempty"`
B *Inner `json:"test2,omitempty"`
}
And then set the one that you do not want to be in the response body to nil. (nil is empty for a pointer, so it will not get marshalled.)

Decoding incoming JSON in Golang based on param values

I am trying to decode an incoming JSON in my REST API written in Go. I am using decoder.Decode() function and my problem is that I need to apply a certain rules on which struct should be used in the process of decoding because sometimes the JSON contains:
"type": {
"type" : "string",
"maxLength" : 30
},
and sometimes:
"type": {
"type" : "integer",
"max" : 30,
"min" : 10
},
I somehow need to tell Go that "If the type.type is string, use this struct (type Type_String struct) and if the type.type is integer, use other struct (type Type_Integer struct)". I am not really sure how to do it. One solution which is on my mind is to make an universal struct with the all possible properties, use it on any kind of object and then filter the properties based on the type property but this is just so dirty. I guess I can also write my own decoder but that seems also a bit strange.
I am new to Go and I am pretty much used to the freedom JavaScript offers.
First of all, if fields of "type" depends on "type.type", in my opinion, it's better to move it one level up. Something like:
...
"type" : "integer",
"intOptions": {
"max" : 30,
"min" : 10
},
....
Then you can create a struct with only one field:
type Type struct {
Type string
}
and do something like:
myType := new(Type)
json.Unmarshal([]byte(yourJsonString), myType)
And now, depending on myType's value you can use different structs for decoding your json.
You can always decode to interface{} like mentioned here: How to access interface fields on json decode?
http://play.golang.org/p/3z8-unhsH4
package main
import (
"encoding/json"
"fmt"
)
var one string = `{"type": {"type": "string", "maxLength":30}}`
var two string = `{"type": {"type": "integer", "max":30, "min":10}}`
func f(data map[string]interface{}) {
t := data["type"]
typemap := t.(map[string]interface{})
t2 := typemap["type"].(string)
switch t2 {
case "string":
fmt.Println("maxlength:", typemap["maxLength"].(float64))
case "integer":
fmt.Println("max:", typemap["max"].(float64))
default:
panic("oh no!")
}
}
func main() {
var jsonR map[string]interface{}
err := json.Unmarshal([]byte(one), &jsonR)
if err != nil {
panic(err)
}
f(jsonR)
json.Unmarshal([]byte(two), &jsonR)
f(jsonR)
}
The idea is to unmarshal to map[string]interface{} and then cast and compare before accessing values.
In the above code, the f function does the cast and compare. Given this poor json, I used poor variable name, t and t2 to represent the json values of "type" at different depths. Once t2 has the value, the switch statement does something with the "string" or the "integer" and what it does is print the maxLength or the max value.