如何将复杂的JSON映射到其他JSON。

huangapple go评论76阅读模式
英文:

how I can mapping complex JSON to other JSON

问题

我正在尝试构建“聚合服务”,用于所有我使用的第三方API,这个“聚合服务”接收来自我的主系统的JSON值,并将该值放入与第三方API密钥等效的键中,然后,“聚合服务”将以新的JSON格式向第三方API发送请求。

示例1:

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/tidwall/gjson"
)

func main() {
    // 映射JSON
    mapB := []byte(`
    {
        "date": "createdAt",
        "clientName": "data.user.name"
    }
    `)

    // 来自我的主系统
    dataB := []byte(`
    {
        "createdAt": "2017-05-17T08:52:36.024Z",
        "data": {
            "user": {
                "name": "xxx"
            }
        }
    }
    `)

    mapJSON := make(map[string]interface{})
    dataJSON := make(map[string]interface{})
    newJSON := make(map[string]interface{})

    err := json.Unmarshal(mapB, &mapJSON)
    if err != nil {
        log.Panic(err)
    }

    err = json.Unmarshal(dataB, &dataJSON)
    if err != nil {
        log.Panic(err)
    }

    for i := range mapJSON {
        r := gjson.GetBytes(dataB, mapJSON[i].(string))
        newJSON[i] = r.Value()
    }

    newB, err := json.MarshalIndent(newJSON, "", "  ")
    if err != nil {
        log.Println(err)
    }

    fmt.Println(string(newB))
}

输出:

{
  "clientName": "xxx",
  "date": "2017-05-17T08:52:36.024Z"
}

我使用gjson包从JSON文档中以简单的方式获取来自我的主系统请求的值。

示例2:

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/tidwall/gjson"
)

func main() {
    // 映射JSON
    mapB := []byte(`
    {
        "date": "createdAt",
        "clientName": "data.user.name",
        "server":{
            "google":{
                "date" :"createdAt"
            }
        }
    }
    `)

    // 来自我的主系统
    dataB := []byte(`
    {
        "createdAt": "2017-05-17T08:52:36.024Z",
        "data": {
            "user": {
                "name": "xxx"
            }
        }
    }
    `)

    mapJSON := make(map[string]interface{})
    dataJSON := make(map[string]interface{})
    newJSON := make(map[string]interface{})

    err := json.Unmarshal(mapB, &mapJSON)
    if err != nil {
        log.Panic(err)
    }

    err = json.Unmarshal(dataB, &dataJSON)
    if err != nil {
        log.Panic(err)
    }

    for i := range mapJSON {
        r := gjson.GetBytes(dataB, mapJSON[i].(string))
        newJSON[i] = r.Value()
    }

    newB, err := json.MarshalIndent(newJSON, "", "  ")
    if err != nil {
        log.Println(err)
    }

    fmt.Println(string(newB))
}

输出:

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

*我可以使用https://golang.org/ref/spec#Type_assertions来处理此错误,但如果此JSON对象具有数组,并且在此数组内部有JSON对象....*

**我的问题是我有不同的API,每个API都有自己的JSON模式,*而我的JSON映射方式只适用于第三方API只有键值对的情况,而没有嵌套的JSON或数组内部的JSON对象。***

是否有一种方法可以映射复杂的JSON模式,或者有没有Golang包可以帮助我做到这一点?

  [1]: https://github.com/tidwall/gjson
英文:

I am trying to build aggregation services, to all third party APIs that's I used,
this aggregation services taking json values coming from my main system and it will put this value to key equivalent to third party api key then, aggregation services it will send request to third party api with new json format.

example-1:

package main

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/tidwall/gjson"
)

func main() {
    // mapping JSON
    mapB := []byte(`
    {
        "date": "createdAt",
        "clientName": "data.user.name"
    }
    `)

    // from my main system
    dataB := []byte(`
    {
        "createdAt": "2017-05-17T08:52:36.024Z",
        "data": {
            "user": {
                "name": "xxx"
            }
        }
    }
    `)

    mapJSON := make(map[string]interface{})
    dataJSON := make(map[string]interface{})
    newJSON := make(map[string]interface{})

    err := json.Unmarshal(mapB, &mapJSON)
    if err != nil {
        log.Panic(err)
    }

    err = json.Unmarshal(dataB, &dataJSON)
    if err != nil {
        log.Panic(err)
    }

    for i := range mapJSON {
        r := gjson.GetBytes(dataB, mapJSON[i].(string))
        newJSON[i] = r.Value()
    }

    newB, err := json.MarshalIndent(newJSON, "", "  ")
    if err != nil {
        log.Println(err)
    }

    fmt.Println(string(newB))
}

output:

{
  "clientName": "xxx",
  "date": "2017-05-17T08:52:36.024Z"
}

I use gjson package to get values form my main system request in simple way from a json document.

example-2:

import (
    "encoding/json"
    "fmt"
    "log"

    "github.com/tidwall/gjson"
)

func main() {
    // mapping JSON
    mapB := []byte(`
    {
        "date": "createdAt",
        "clientName": "data.user.name",
        "server":{
            "google":{
                "date" :"createdAt"
            }
        }
    }
    `)

    // from my main system
    dataB := []byte(`
    {
        "createdAt": "2017-05-17T08:52:36.024Z",
        "data": {
            "user": {
                "name": "xxx"
            }
        }
    }
    `)

    mapJSON := make(map[string]interface{})
    dataJSON := make(map[string]interface{})
    newJSON := make(map[string]interface{})

    err := json.Unmarshal(mapB, &mapJSON)
    if err != nil {
        log.Panic(err)
    }

    err = json.Unmarshal(dataB, &dataJSON)
    if err != nil {
        log.Panic(err)
    }

    for i := range mapJSON {
        r := gjson.GetBytes(dataB, mapJSON[i].(string))
        newJSON[i] = r.Value()
    }

    newB, err := json.MarshalIndent(newJSON, "", "  ")
    if err != nil {
        log.Println(err)
    }

    fmt.Println(string(newB))
}

output:

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

I can handle this error by using https://golang.org/ref/spec#Type_assertions, but what if this json object have array and inside this array have json object ....

my problem is I have different apis, every api have own json schema, and my way for mapping json only work if
third party api have json key value only, without nested json or array inside this array json object.

is there a way to mapping complex json schema, or golang package to help me to do that?

答案1

得分: 2

编辑:

在评论互动和更新问题后。在我们继续之前,我想提一下。

> 我刚刚看了你的example-2。记住一件事,映射是从一种形式到另一种形式的转换。基本上是从“已知格式到目标格式”的映射。每种数据类型都必须处理。你不能在逻辑上进行“通用”到“通用”的映射(尽管在技术上是可行的,但需要更多的时间和精力,你可以在此上进行尝试)。

我创建了一个示例工作程序,它将源格式映射到目标格式。将此程序作为起点,并发挥你的创造力来实现你自己的程序。

Playground链接:https://play.golang.org/p/MEk_nGcPjZ

**解释:**示例程序实现了两种不同的源格式到一个目标格式的映射。程序包括以下内容:

  • Provider 1的目标映射定义
  • Provider 2的目标映射定义
  • Provider 1的JSON
  • Provider 2的JSON
  • 映射函数
  • 目标JSON编组

**程序中的关键元素:**完整程序请参考play链接。

type MappingInfo struct {
    TargetKey     string
    SourceKeyPath string
    DataType      string
}

映射函数:

func mapIt(mapping []*MappingInfo, parsedResult gjson.Result) map[string]interface{} {
    mappedData := make(map[string]interface{})
    for _, m := range mapping {
        switch m.DataType {
        case "time":
            mappedData[m.TargetKey] = parsedResult.Get(m.SourceKeyPath).Time()
        case "string":
            mappedData[m.TargetKey] = parsedResult.Get(m.SourceKeyPath).String()
        }
    }
    return mappedData
}

输出:

Provider 1的结果:map[date:2017-05-17 08:52:36.024 +0000 UTC clientName:provider1 username]
Provider 1的JSON:{
  "clientName": "provider1 username",
  "date": "2017-05-17T08:52:36.024Z"
}

Provider 2的结果:map[date:2017-05-12 06:32:46.014 +0000 UTC clientName:provider2 username]
Provider 2的JSON:{
  "clientName": "provider2 username",
  "date": "2017-05-12T06:32:46.014Z"
}

祝你好运,编码愉快!


通常,将一种结构转换为另一种结构,你需要使用应用程序逻辑来处理这个问题。

正如你在问题中提到的:

> 我的问题是,我有不同的API,每个API都有自己的JSON模式

这对于每个“聚合”系统来说都是正确的。


处理这个要求的一种有效方法是为每个提供者的JSON结构和目标JSON结构保留键的映射。

**例如:**这只是一种方法,请根据你自己的设计进行调整。

来自各个提供者的JSON结构:

// Provider 1的JSON结构
{
  "createdAt": "2017-05-17T08:52:36.024Z",
  "data": {
    "user": {
      "name": "xxx"
    }
  }
}

// Provider 2的JSON结构
{
  "username": "yyy",
  "since": "2017-05-17T08:52:36.024Z",
}

目标JSON结构的映射:

jsonMappingByProvider := make(map[string]string)

// Provider 1的目标映射
jsonMappingByProvider["provider1"] = `
{
    "date": "createdAt",
    "clientName": "data.user.name"
}
`

// Provider 2的目标映射
jsonMappingByProvider["provider2"] = `
{
    "date": "since",
    "clientName": "username"
}
`

现在,根据你正在处理的提供者,获取映射并将响应的JSON映射到目标结构。

// 根据提供者获取映射信息
mapping := jsonMappingByProvider["provider1"]

// 解析响应的JSON
// 进行映射

通过这种方式,你可以有效地控制每个提供者及其映射。

英文:

EDIT:

After comment interaction and with updated question. Before we move forward, I would like to mention.

> I just looked at your example-2 Remember one thing. Mapping is from one form to another form. Basically one known format to targeted format. Each data type have to handled. You cannot do generic to generic mapping logically (technically feasible though, would take more time & efforts, you can play around on this).

I have created sample working program of one approach; it does a mapping of source to targeted format. Refer this program as a start point and use your creativity to implement yours.

Playground link: https://play.golang.org/p/MEk_nGcPjZ

Explanation: Sample program achieves two different source format to one target format. The program consist of -

  • Targeted Mapping definition of Provider 1
  • Targeted Mapping definition of Provider 2
  • Provider 1 JSON
  • Provider 2 JSON
  • Mapping function
  • Targeted JSON marshal

Key elements from program: refer play link for complete program.

type MappingInfo struct {
    TargetKey     string
    SourceKeyPath string
    DataType      string
}

Map function:

func mapIt(mapping []*MappingInfo, parsedResult gjson.Result) map[string]interface{} {
	mappedData := make(map[string]interface{})
	for _, m := range mapping {
		switch m.DataType {
		case "time":
			mappedData[m.TargetKey] = parsedResult.Get(m.SourceKeyPath).Time()
		case "string":
			mappedData[m.TargetKey] = parsedResult.Get(m.SourceKeyPath).String()
		}
	}
	return mappedData
}

Output:

Provider 1 Result: map[date:2017-05-17 08:52:36.024 +0000 UTC clientName:provider1 username]
Provider 1 JSON: {
  "clientName": "provider1 username",
  "date": "2017-05-17T08:52:36.024Z"
}

Provider 2 Result: map[date:2017-05-12 06:32:46.014 +0000 UTC clientName:provider2 username]
Provider 2 JSON: {
  "clientName": "provider2 username",
  "date": "2017-05-12T06:32:46.014Z"
}

Good luck, happy coding!


Typically Converting/Transforming one structure to another structure, you will have to handle this with application logic.

As you mentioned in the question:

> my problem is I have different apis, every api have own json schema

This is true for every aggregation system.


One approach to handle this requirement effectively; is to keep mapping of keys for each provider JSON structure and targeted JSON structure.

For example: This is an approach, please go with your design as you see fit.

JSON structures from various provider:

// Provider 1 : JSON structrure
{
  "createdAt": "2017-05-17T08:52:36.024Z",
  "data": {
    "user": {
      "name": "xxx"
    }
  }
}

// Provider 2 : JSON structrure
{
  "username": "yyy"
  "since": "2017-05-17T08:52:36.024Z",
}

Mapping for target JSON structure:

jsonMappingByProvider := make(map[string]string)

// Targeted Mapping for Provider 1
jsonMappingByProvider["provider1"] = `
{
    "date": "createdAt",
    "clientName": "data.user.name"
}
`

// Targeted Mapping for Provider 2
jsonMappingByProvider["provider2"] = `
{
    "date": "since",
    "clientName": "username"
}
`

Now, based the on the provider you're handling, get the mapping and map the response JSON into targeted structure.

// get the mapping info by provider
mapping := jsonMappingByProvider["provider1"]

// Parse the response JSON 
// Do the mapping

This way you can control each provider and it's mapping effectively.

huangapple
  • 本文由 发表于 2017年7月13日 03:04:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/45065678.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定