0

I'm new in golang development and have some question regarding something related to this question.

As a learning exercise, I'm trying to create a simple library to handle json based configuration file. As a configuration file to be used for more then one app, it should be able to handle different parameters. Then I have created a type struct Configuration that has the filename and a data interface. Each app will have a struct based on its configuration needs.

In the code bellow, I put all together (lib and "main code") and the "TestData struct" is the "app parameters". If it doesn't exists, it will set a default values and create the file, and it is working. But when I try to read the file. I try to decode the json and put it back into the data interface. But it is giving me an error and I couldn't figure out how to solve this. Can someone help on this?

[updated] I didn't put the targeted code before, because I though that it would be easier to read in in all as a single program. Bellow is the 'targeted code' for better view of the issue. As I will not be able to use the TestData struct inside the library, since it will change from program to program, the only way to handle this was using interface. Is there a better way?

library config

package config

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

// Base configuration struct
type Configuration struct {
    Filename string
    Data     interface{}
}

func (c *Configuration) Create(cData *Configuration) bool {
    cFile, err := os.Open(cData.Filename)
    defer cFile.Close()
    if err == nil {
        fmt.Println("Error(1) trying to create a configuration file. File '", cData.Filename, "' may already exist...")
        return false
    }
    cFile, err = os.Create(cData.Filename)
    if err != nil {
        fmt.Println("Error(2) trying to create a configuration file. File '", cData.Filename, "' may already exist...")
        return false
    }
    buffer, _ := json.MarshalIndent(cData.Data, "", "")
    cFile.Write(buffer)
    return true
}

func (c *Configuration) Read(cData *Configuration) bool {
    cFile, err := os.Open(cData.Filename)
    defer cFile.Close()
    if err != nil {
        fmt.Println("Error(1) trying to read a configuration file. File '", cData.Filename, "' may not already exist...")
        return false
    }
    jConfig := json.NewDecoder(cFile)
    jerr := jConfig.Decode(&cData.Data)
    if jerr != nil {
        panic(jerr)
    }
    return true
}

program using library config

package main

import (
    "fmt"

    "./config"
)

// struct basic para configuração
type TestData struct {
    URL  string
    Port string
}

func main() {
    var Config config.Configuration
    Config.Filename = "config.json"

    if !Config.Read(&Config) {
        Config.Data = TestData{"http", "8080"}
        Config.Create(&Config)
    }
    fmt.Println(Config.Data)
    TestData1 := &TestData{}
    TestData1 = Config.Data.(*TestData) // error, why?
    fmt.Println(TestData1.URL)
}

NEW UPDATE: I have made some changes after JimB comment about I'm not clear about some concepts and I tried to review it. Sure many things aren't clear for me yet unfortunately. The "big" understanding I believe I got, but what mess my mind up is the "ins" and "outs" of values and formats and pointers, mainly when it goes to other libraries. I'm not able yet to follow the "full path" of it.

Yet, I believe I had some improvement on my code.

I think that I have corrected some points, but still have some big questions:

  1. I stopped sending "Configuration" as a parameter as all "data" were already there as they are "thenselfs" in the instance. Right?
  2. Why do I have use reference in the line 58 (Config.Data = &TestData{})
  3. Why to I have to use pointer in the line 64 (tmp := Config.Data.(*TestData)
  4. Why I CANNOT use reference in line 69 (Config.Data = tmp)

Thanks

Community
  • 1
  • 1
Tuts
  • 25
  • 1
  • 6

2 Answers2

0

You are trying to assert that Config.Data is of type *TestData, but you're assigning it to TestData{"http", "8080"} above. You can take the address of a composite literal to create a pointer:

Config.Data = &TestData{"http", "8080"}

If your config already exsits, your Read method is going to fill in the Data field with the a default json data type, probably a map[string]interface{}. If you assign a pointer of the correct type to Data first, it will decode into the expected type.

Config.Data = &TestData{}

Ans since Data is an interface{}, you do not want to ever use a pointer to that value, so don't use the & operator when marshaling and unmarshaling.

JimB
  • 104,193
  • 13
  • 262
  • 255
  • Thanks for your answer. I have tried this and still didn't work. – Tuts Feb 10 '16 at 23:12
  • @Tuts: please show what you tried, what the error was, and what you expect. Something like this works (aside from many other issues) http://play.golang.org/p/UySh4Nbw0Z – JimB Feb 10 '16 at 23:15
  • Hi JimB, yours changes worked on most cases, but still have some issues sometimes. I'm trying to figure out. Thanks – Tuts Feb 11 '16 at 17:17
  • @Tuts: judging by the code, you're still not clear on some of the primary concepts, like pointers, method receivers, etc. Have you run through the [Tour Of Go](http://tour.golang.org/)? – JimB Feb 11 '16 at 17:22
  • Hi JimB, sorry for long time to answer, but I wasn't able to get back to this tests till now. – Tuts Feb 19 '16 at 23:01
0

The reason you are running into an error is because you are trying to decode into an interface{} type. When dealing with JSON objects, they are decoded by the encoding/json package into map[string]interface{} types by default. This is causing the type assertion to fail since the memory structure for a map[string]interface{} is much different than that of a struct.

The better way to do this is to make your TestData struct the expected data format for your Configuration struct:

// Base configuration struct
type Configuration struct {
    Filename string
    Data     *TestData
}

Then when Decoding the file data, the package will unmarshal the data into the fields that match the closest with the data it finds.

If you need more control over the data unmarshaling process, you can dictate which JSON fields get decoded into which struct members by using struct tags. You can read more about the json struct tags available here: https://golang.org/pkg/encoding/json/#Marshal

Corvus Crypto
  • 2,141
  • 1
  • 13
  • 14
  • 1
    The problem isn't decoding into an `interface{}` type, all json decoding is into an `interface{}` type. The problem is that it's a `nil` `interface{}` which is where it can return the default type. If a pointer of the proper type is assigned to `Data`, it will unmarshal correctly. – JimB Feb 10 '16 at 22:55
  • yes, like I said, the decoding isn't the problem, it's the assertion because you are trying to assert to an entirely different data structure. Thanks for clearing up my language! – Corvus Crypto Feb 10 '16 at 23:01
  • Thanks for your answer. I was doing that until I changed to this code to become a library. Then, as the content on the Configuration.Data will be unknow by the library, I had to use interface instead since the 'sample' struct isn't inside the library, I will not be able to use as you`d suggested. – Tuts Feb 10 '16 at 23:08
  • Hi JimB, yours changes worked on most cases, but still have some issues sometimes. I'm trying to figure out. Thanks – Tuts Feb 11 '16 at 17:17