MelDev
MelDev

Reputation: 295

How to convert a map[string]interface{} to string

I'm trying to get the path to value from a json file:

for example :

x: 4
z:
  y: 6
  t: 8
a:
  b:
    p: 0
m:
  c : 4 

the output that i want is :

x=4, z.y=6, z.t=8, a.b.p=0, m.c=4

i wrote a functions below :

func parsecustom(aMap map[string]interface{}) (string, string) {

    var sol string
    var k string
    for key, val := range aMap {
        switch concreteVal := val.(type) {
        case map[string]interface{}:
            x, _ := parseMap(val.(map[string]interface{}))
            sol = key + "." + x
            k += sol + ","
        case string:
            sol = key + "=" + concreteVal + " "
            k += sol + ","
        case float64:
            sol = key + "=" + strconv.FormatFloat(concreteVal, 'f', -1, 64) + " "
            k+= sol + ","
        default:
            k = " "

        }
    }

return sol, TrimSuffix(k, ",")
}

but it didn't take into account this case :

z:
  y: 6
  t: 8

it override, it print only z.t=8 and ignore z.y=6

Any hints please how to solve this problem

Upvotes: 3

Views: 6353

Answers (2)

Zak
Zak

Reputation: 5898

In this part of the switch, where you recurse, you are only considering the return value of sol which is the last element to be parsed.

    case map[string]interface{}:
        x, _ := parseMap(val.(map[string]interface{}))

Given the values (assuming they will be iterated in order, not strictly true as maps are unordered):

z:
  y: 6
  t: 8

You would end up with a return from the recursive call as:

sol:  "t=8" 
k:    "y=6 ,t=8 ,"

Then you take the first return value sol, call it variable x and prepend "z." to it. This means that you ignore the "y=6" part that is in k. You are only considering sol, which is the last value to be parsed by the recursive call.

Here is a runnable example, copy pasted from code in the question: https://play.golang.org/p/F8gwq9YMxWu

Update - solution:

Instead of attempting to return multiple things from each of the calls to parse, it would be easier to pass a string, a prefix, that should be added to all the values lower down the call stack. This way the values that are returned already have the desired end value. And can just be added to resulting string.

func parse(prefix string, m map[string]interface{}) string {
    if len(prefix) > 0 { // only add the . if this is not the first call.
        prefix = prefix + "."
    }

    // builder stores the results string, appended to it 
    var builder string
    for mKey, mVal := range m {

        // update a local prefix for this map key / value combination
        pp := prefix + mKey

        switch typedVal := mVal.(type) {
        case string:
            builder += fmt.Sprintf("%s%s, ", pp, typedVal)
        case float64:
            builder += fmt.Sprintf("%s.%-1.0f, ", pp, typedVal)
        case map[string]interface{}:
            // add all the values to the builder, you already know they are correct.
            builder += parse(pp, typedVal)
        }
    }

    // return the string that this call has built
    return builder
}

To call it for the very first time, pass a prefix of ""

result := parse("", m)

Runnable example solution https://play.golang.org/p/Y-m9rQCY0xw

Upvotes: 1

leaf bebop
leaf bebop

Reputation: 8212

To address the problem that child values should bearing parents' name, I think when recursing, there needs to be a prefix parameter. And instead of asking the parents to insert those prefix into the string, it would feel more normal to let the children write the prefix just as they are going to write their own parsed results. So I changed your code:

func parseMap(aMap map[string]interface{}, prefix string, b *strings.Builder) {
    var sol string
    for key, val := range aMap {
        switch concreteVal := val.(type) {
        case map[string]interface{}:
            parseMap(concreteVal,prefix+key+".",b)
        case string:
            sol = key + "=" + concreteVal + " "
            b.WriteString(prefix)
            b.WriteString(sol)
            b.WriteRune(',')
        case float64:
            sol = key + "=" + strconv.FormatFloat(concreteVal, 'f', -1, 64) + " "
            b.WriteString(prefix)
            b.WriteString(sol)
            b.WriteRune(',')
        default:
            //What?
            panic("Unsupported")
        }
    }
}

Since we are concating a lot of strings here, I used the strings.Builder for string building. b.WriteString(x) is just appeding x to the tail of the string b holds.

And because we now use a strings.builder and a prefix, so I write a function to wrap it:

func parsecustom(aMap map[string]interface{}) string {
    b:=&strings.Builder{}
    parseMap(aMap,"",b)

    return strings.TrimSuffix(b.String(),",")
}

Now if you run fmt.Println(parsecustom(data)), you can get output: x=4 ,z.t=8 ,z.y=6 ,a.b.p=0 ,m.c=4 as desired.

Full example: https://play.golang.org/p/_QIp2LRYjm7

Upvotes: 2

Related Questions